create-ailk 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +336 -27
  3. package/component-catalog.md +25 -12
  4. package/dist/lib/api-boot-assertion.d.ts +46 -0
  5. package/dist/lib/api-boot-assertion.js +70 -0
  6. package/dist/lib/import-graph.d.ts +29 -0
  7. package/dist/lib/import-graph.js +140 -0
  8. package/dist/module-architecture.d.ts +1 -0
  9. package/dist/module-architecture.js +253 -127
  10. package/dist/surfaces.js +105 -7
  11. package/dist/template-manifest.d.ts +33 -0
  12. package/dist/template-manifest.js +99 -16
  13. package/dist/template-names.d.ts +1 -1
  14. package/dist/template-names.js +1 -0
  15. package/package.json +3 -2
  16. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
  17. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
  18. package/site-templates/marketing-site/ailk-template.json +1 -1
  19. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
  20. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
  21. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
  22. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
  23. package/site-templates/minimalism-site/ailk-template.json +2 -1
  24. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  25. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
  26. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  27. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  28. package/site-templates/simple-service/ailk-template.json +10 -0
  29. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  30. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  31. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  32. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  33. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  34. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  35. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  36. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  37. package/site-templates/simple-service/provenance.json +4 -0
  38. package/templates/.env.example +72 -7
  39. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  40. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  41. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  42. package/templates/apps/api/.env.example +13 -0
  43. package/templates/apps/api/CLAUDE.md +26 -2
  44. package/templates/apps/api/jest.config.cjs +3 -0
  45. package/templates/apps/api/package.json +1 -0
  46. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  47. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
  48. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  49. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  50. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
  51. package/templates/apps/api/src/config/index.ts +62 -0
  52. package/templates/apps/api/src/config/modules.ts +7 -3
  53. package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
  54. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  55. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  56. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  57. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  58. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  59. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  60. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  61. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  62. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  63. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  64. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  65. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  66. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  67. package/templates/apps/api/src/lib/site.ts +208 -9
  68. package/templates/apps/api/src/lib/stripe.ts +66 -0
  69. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
  70. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
  71. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  72. package/templates/apps/api/src/openapi/spec.ts +543 -2
  73. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  74. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  75. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  76. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  77. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  78. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  79. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  80. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  81. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  82. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  83. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  84. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  85. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  86. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  87. package/templates/apps/api/src/routes/content/index.ts +13 -73
  88. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  89. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  90. package/templates/apps/api/src/routes/content/search.ts +129 -0
  91. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  92. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  93. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  94. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  95. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  96. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  97. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  98. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  99. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  100. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  101. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  102. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  103. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  104. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  105. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  106. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  111. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  112. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  113. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  114. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  115. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  116. package/templates/apps/api/src/server.ts +439 -49
  117. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  118. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  119. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  120. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  121. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  122. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  123. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  124. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  125. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  126. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  127. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  128. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  129. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  130. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  131. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  132. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  133. package/templates/apps/api/src/services/audit-log.ts +668 -0
  134. package/templates/apps/api/src/services/consent.ts +18 -0
  135. package/templates/apps/api/src/services/default-tenant.ts +70 -31
  136. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  137. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  138. package/templates/apps/api/src/services/entitlements.ts +80 -0
  139. package/templates/apps/api/src/services/lead-promotion.ts +13 -14
  140. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  141. package/templates/apps/api/src/services/leads-inbox.ts +23 -2
  142. package/templates/apps/api/src/services/listing-config.ts +27 -9
  143. package/templates/apps/api/src/services/project-listings.ts +201 -6
  144. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  145. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  146. package/templates/apps/api/src/services/revalidation.ts +173 -0
  147. package/templates/apps/api/src/services/verified-email.ts +71 -0
  148. package/templates/apps/api/src/vercel-handler.ts +75 -35
  149. package/templates/apps/mcp/.env.example +17 -0
  150. package/templates/apps/mcp/CLAUDE.md +57 -24
  151. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  152. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  153. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  154. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  155. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  156. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  157. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  158. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  159. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  160. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  161. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  162. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  163. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  164. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  165. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  166. package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
  167. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  168. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  169. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  170. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  171. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  172. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  173. package/templates/apps/mcp/jest.config.cjs +13 -0
  174. package/templates/apps/mcp/package.json +2 -0
  175. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  176. package/templates/apps/mcp/src/config/modules.ts +10 -5
  177. package/templates/apps/mcp/src/http-transport.ts +102 -29
  178. package/templates/apps/mcp/src/index.ts +73 -11
  179. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  180. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  181. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  182. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  183. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  184. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  185. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  186. package/templates/apps/mcp/src/server.ts +59 -35
  187. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  188. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  189. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  190. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  191. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  192. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  193. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  194. package/templates/apps/mcp/src/tools/index.ts +6 -4
  195. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  196. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  197. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  198. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  199. package/templates/apps/web/.env.example +71 -5
  200. package/templates/apps/web/CLAUDE.md +44 -0
  201. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  202. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  203. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  204. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  205. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  206. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  207. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  208. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  209. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  210. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  211. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  212. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  213. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  214. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  215. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  216. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  217. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  218. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  219. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  220. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
  221. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
  222. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  223. package/templates/apps/web/app/[locale]/layout.tsx +133 -87
  224. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  225. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  226. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  227. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  228. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  229. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  230. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  231. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  232. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  233. package/templates/apps/web/auth.ts +407 -8
  234. package/templates/apps/web/jest.config.cjs +8 -4
  235. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  236. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  237. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  238. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
  239. package/templates/apps/web/lib/auth-email.ts +318 -0
  240. package/templates/apps/web/lib/auth-env.ts +85 -0
  241. package/templates/apps/web/lib/auth-routes.ts +55 -0
  242. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  243. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  244. package/templates/apps/web/messages/en.json +187 -0
  245. package/templates/apps/web/package.json +2 -1
  246. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  247. package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
  248. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  249. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  250. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  251. package/templates/database/CHANGELOG.md +142 -0
  252. package/templates/database/README.md +10 -3
  253. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  254. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  255. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  256. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  257. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  258. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  259. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  260. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  261. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  262. package/templates/database/__tests__/tenancy.test.ts +141 -0
  263. package/templates/database/config/schema.prisma +79 -2
  264. package/templates/database/content/schema.prisma +87 -11
  265. package/templates/database/inbox/schema.prisma +452 -9
  266. package/templates/database/jest.config.cjs +5 -3
  267. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  268. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  269. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  270. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  271. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  272. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  273. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  274. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  275. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  276. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  277. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  278. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  279. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  280. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  281. package/templates/database/package.json +1 -1
  282. package/templates/database/tenancy.ts +86 -1
  283. package/templates/database/tsconfig.json +1 -0
  284. package/versions.json +34 -34
  285. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  286. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  287. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  288. package/templates/apps/api/src/routes/content/create.ts +0 -115
  289. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  290. package/templates/apps/api/src/routes/content/update.ts +0 -112
  291. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  292. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  293. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  294. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  295. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  296. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  297. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  298. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  299. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
@@ -1,112 +0,0 @@
1
- // oss: false
2
- /**
3
- * PATCH /v1/content/:type/:slug
4
- *
5
- * Updates an existing page of `:type` at `:slug`. Paid-licensed deploys only.
6
- *
7
- * Optimistic locking is mandatory: the request MUST carry an `If-Match`
8
- * header containing the `version` token returned by a prior read or write.
9
- * Missing/empty header → 409. Stale token → 409 with `details.currentVersion`
10
- * echoed so the caller can re-fetch and retry.
11
- *
12
- * 404 when the page does not exist.
13
- *
14
- * Adapter errors (`AdapterWriteError("not_found" | "conflict" | …)`) are
15
- * translated by the hoisted `mapAdapterError` from `@working-theory/content-adapters`
16
- * (issue #398) — single source of truth across the API + MCP surfaces.
17
- */
18
- import {
19
- FrontmatterSchema,
20
- SCHEMA_ORG_TYPES,
21
- type SchemaOrgType,
22
- } from "@working-theory/content-adapters";
23
- import type { FastifyPluginAsync, FastifyReply } from "fastify";
24
- import { z } from "zod";
25
-
26
- import { getContentAdapter } from "../../lib/content-adapter.js";
27
- import {
28
- adapterErrorResponse,
29
- conflictError,
30
- validationError,
31
- type ErrorBody,
32
- } from "../../lib/errors.js";
33
- import { requirePaidTier } from "../../lib/require-paid-tier.js";
34
-
35
- import { SLUG_PATTERN } from "./slug.js";
36
-
37
- const paramsSchema = z.object({
38
- type: z.enum(SCHEMA_ORG_TYPES),
39
- slug: z.string().regex(SLUG_PATTERN),
40
- });
41
-
42
- const writeBodySchema = FrontmatterSchema.extend({ body: z.string().min(1) });
43
-
44
- function send(reply: FastifyReply, status: number, body: ErrorBody) {
45
- return reply.status(status).send(body);
46
- }
47
-
48
- export const updatePageRoute: FastifyPluginAsync = async (fastify) => {
49
- fastify.patch<{
50
- Params: { type: string; slug: string };
51
- Body: unknown;
52
- }>(
53
- "/v1/content/:type/:slug",
54
-
55
- { preHandler: requirePaidTier },
56
- async (request, reply) => {
57
- const params = paramsSchema.safeParse(request.params);
58
- if (!params.success) {
59
- const err = validationError("Invalid type or slug", {
60
- issues: params.error.issues,
61
- });
62
- return send(reply, err.status, err.body);
63
- }
64
-
65
- const ifMatch = request.headers["if-match"];
66
- if (typeof ifMatch !== "string" || ifMatch.length === 0) {
67
- const err = conflictError(
68
- "Missing required If-Match header (version token)",
69
- );
70
- return send(reply, err.status, err.body);
71
- }
72
-
73
- const parsed = writeBodySchema.safeParse(request.body);
74
- if (!parsed.success) {
75
- const err = validationError("Invalid request body", {
76
- issues: parsed.error.issues,
77
- });
78
- return send(reply, err.status, err.body);
79
- }
80
-
81
- const { type, slug } = params.data;
82
- const { body: pageBody, ...page } = parsed.data;
83
-
84
- if (page.type !== type || page.slug !== slug) {
85
- const err = validationError(
86
- "Body `type`/`slug` must match URL params",
87
- {
88
- url: { type, slug },
89
- body: { type: page.type, slug: page.slug },
90
- },
91
- );
92
- return send(reply, err.status, err.body);
93
- }
94
-
95
- try {
96
- const adapter = getContentAdapter();
97
- const result = await adapter.updatePage(
98
- type as SchemaOrgType,
99
- slug,
100
- { ...page, body: pageBody },
101
- { ifMatch },
102
- );
103
- return reply.status(200).send(result);
104
- } catch (error) {
105
- const err = adapterErrorResponse(error, { type, slug });
106
- if (err) return send(reply, err.status, err.body);
107
- request.log.error({ err: error }, "[Content] updatePage failed");
108
- return reply.status(500).send({ error: "Internal server error" });
109
- }
110
- },
111
- );
112
- };
@@ -1,202 +0,0 @@
1
- // oss: false
2
- /**
3
- * Tests for the create_page MCP tool handler.
4
- *
5
- * D14a Stage 1 Unit C — Layer 1 / Layer 2 acceptance criteria mapped here:
6
- * - Free tier → canonical payment_required body, no adapter call.
7
- * - Bad frontmatter → validation_failed, no adapter call.
8
- * - Slug-already-exists → conflict body with details.existingVersion echoed
9
- * (so the agent can pivot to update without re-fetch).
10
- * - Pro tier + valid input + new slug → { page, version } success body.
11
- *
12
- * Adapter is injected as the second argument so this unit test does not need
13
- * Stage 1 Unit A's MDX implementation in place. The default adapter resolution
14
- * (createMdxAdapter against process.cwd()/content) is exercised by integration
15
- * tests once Unit A lands.
16
- */
17
-
18
- import { AdapterWriteError, type ContentAdapter } from "@working-theory/content-adapters";
19
-
20
- const mockCheckLicense = jest.fn();
21
-
22
- jest.mock("@working-theory/license", () => ({
23
- checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
24
- }));
25
-
26
- import { ailkCreatePage } from "../src/tools/create_page.js";
27
-
28
- interface AdapterCalls {
29
- createPage: jest.Mock;
30
- getPageBySlug: jest.Mock;
31
- getPageVersion: jest.Mock;
32
- }
33
-
34
- function makeAdapter(overrides: Partial<AdapterCalls> = {}): ContentAdapter {
35
- const calls: AdapterCalls = {
36
- createPage: jest.fn(),
37
- getPageBySlug: jest.fn().mockResolvedValue(null),
38
- getPageVersion: jest.fn(),
39
- ...overrides,
40
- };
41
- // eslint-disable-next-line no-restricted-syntax -- #506/#507: pre-existing test-hygiene violation
42
- return {
43
- getPageBySlug: calls.getPageBySlug,
44
- listPages: jest.fn().mockResolvedValue([]),
45
- getDetailBySlug: jest.fn().mockResolvedValue(null),
46
- listDetail: jest.fn().mockResolvedValue([]),
47
- searchPages: jest.fn().mockResolvedValue([]),
48
- extractStructuredData: jest.fn().mockResolvedValue(null),
49
- createPage: calls.createPage,
50
- updatePage: jest.fn(),
51
- deletePage: jest.fn(),
52
- getPageVersion: calls.getPageVersion,
53
- } as unknown as ContentAdapter;
54
- }
55
-
56
- const VALID_INPUT = {
57
- type: "Product" as const,
58
- slug: "widget-pro",
59
- page: {
60
- title: "Widget Pro",
61
- description: "The professional widget.",
62
- },
63
- body: "# Widget Pro\n\nProfessional-grade widget.",
64
- };
65
-
66
- describe("ailkCreatePage", () => {
67
- beforeEach(() => {
68
- mockCheckLicense.mockReset();
69
- });
70
-
71
- describe("license gating", () => {
72
- it("returns canonical payment_required body for free tier; adapter not called", async () => {
73
- mockCheckLicense.mockResolvedValue({
74
- tier: "free",
75
- plan: null,
76
- features: [],
77
- expiresAt: null,
78
- revoked: false,
79
- });
80
- const adapter = makeAdapter();
81
-
82
- const result = await ailkCreatePage(VALID_INPUT, adapter);
83
-
84
- expect(result).toMatchObject({ error: "payment_required" });
85
- expect(adapter.createPage).not.toHaveBeenCalled();
86
- });
87
- });
88
-
89
- describe("validation", () => {
90
- beforeEach(() => {
91
- mockCheckLicense.mockResolvedValue({
92
- tier: "pro",
93
- plan: "pro",
94
- features: [],
95
- expiresAt: null,
96
- revoked: false,
97
- });
98
- });
99
-
100
- it("returns validation_failed when frontmatter is missing required fields", async () => {
101
- const adapter = makeAdapter();
102
- const result = await ailkCreatePage(
103
- {
104
- ...VALID_INPUT,
105
- page: { title: "Only title" }, // missing description
106
- },
107
- adapter,
108
- );
109
- expect(result).toMatchObject({ error: "validation_failed" });
110
- expect(adapter.createPage).not.toHaveBeenCalled();
111
- });
112
-
113
- it("returns validation_failed when body is empty", async () => {
114
- const adapter = makeAdapter();
115
- const result = await ailkCreatePage(
116
- { ...VALID_INPUT, body: "" },
117
- adapter,
118
- );
119
- expect(result).toMatchObject({ error: "validation_failed" });
120
- expect(adapter.createPage).not.toHaveBeenCalled();
121
- });
122
-
123
- it("returns validation_failed for unknown SchemaOrgType", async () => {
124
- const adapter = makeAdapter();
125
- const result = await ailkCreatePage(
126
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
- { ...VALID_INPUT, type: "NotAType" as any },
128
- adapter,
129
- );
130
- expect(result).toMatchObject({ error: "validation_failed" });
131
- expect(adapter.createPage).not.toHaveBeenCalled();
132
- });
133
- });
134
-
135
- describe("slug conflict", () => {
136
- it("returns conflict with details.existingVersion when slug already exists", async () => {
137
- mockCheckLicense.mockResolvedValue({
138
- tier: "pro",
139
- plan: "pro",
140
- features: [],
141
- expiresAt: null,
142
- revoked: false,
143
- });
144
- const adapter = makeAdapter({
145
- createPage: jest
146
- .fn()
147
- .mockRejectedValue(
148
- new AdapterWriteError(
149
- "conflict",
150
- "Page already exists at Product/widget-pro.mdx",
151
- { version: "abc123" },
152
- ),
153
- ),
154
- });
155
-
156
- const result = await ailkCreatePage(VALID_INPUT, adapter);
157
-
158
- expect(result).toMatchObject({
159
- error: "conflict",
160
- details: { version: "abc123" },
161
- });
162
- });
163
- });
164
-
165
- describe("happy path", () => {
166
- it("returns { page, version } when adapter succeeds", async () => {
167
- mockCheckLicense.mockResolvedValue({
168
- tier: "pro",
169
- plan: "pro",
170
- features: [],
171
- expiresAt: null,
172
- revoked: false,
173
- });
174
- const persistedPage = {
175
- type: "Product" as const,
176
- slug: "widget-pro",
177
- title: "Widget Pro",
178
- description: "The professional widget.",
179
- };
180
- const adapter = makeAdapter({
181
- createPage: jest
182
- .fn()
183
- .mockResolvedValue({ page: persistedPage, version: "v1" }),
184
- });
185
-
186
- const result = await ailkCreatePage(VALID_INPUT, adapter);
187
-
188
- expect(result).toEqual({ page: persistedPage, version: "v1" });
189
- expect(adapter.createPage).toHaveBeenCalledTimes(1);
190
- const callArgs = (adapter.createPage as jest.Mock).mock.calls[0];
191
- expect(callArgs[0]).toBe("Product");
192
- expect(callArgs[1]).toBe("widget-pro");
193
- expect(callArgs[2]).toMatchObject({
194
- type: "Product",
195
- slug: "widget-pro",
196
- title: "Widget Pro",
197
- description: "The professional widget.",
198
- body: "# Widget Pro\n\nProfessional-grade widget.",
199
- });
200
- });
201
- });
202
- });
@@ -1,177 +0,0 @@
1
- // oss: false
2
- /**
3
- * Tests for the delete_page MCP tool handler.
4
- *
5
- * D14a Stage 1 Unit C — Layer 1 / Layer 2 acceptance criteria mapped here:
6
- * - Free tier → payment_required, no adapter call.
7
- * - Missing ifMatch → validation_failed.
8
- * - Stale ifMatch → conflict with details.currentVersion echoed.
9
- * - Slug not found → not_found body.
10
- * - Pro + matching ifMatch → { ok: true } success body.
11
- */
12
-
13
- import { AdapterWriteError, type ContentAdapter } from "@working-theory/content-adapters";
14
-
15
- const mockCheckLicense = jest.fn();
16
-
17
- jest.mock("@working-theory/license", () => ({
18
- checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
19
- }));
20
-
21
- import { ailkDeletePage } from "../src/tools/delete_page.js";
22
-
23
- function makeAdapter(
24
- overrides: Partial<{ deletePage: jest.Mock }> = {},
25
- ): ContentAdapter {
26
- // eslint-disable-next-line no-restricted-syntax -- #506/#507: pre-existing test-hygiene violation
27
- return {
28
- getPageBySlug: jest.fn().mockResolvedValue(null),
29
- listPages: jest.fn().mockResolvedValue([]),
30
- getDetailBySlug: jest.fn().mockResolvedValue(null),
31
- listDetail: jest.fn().mockResolvedValue([]),
32
- searchPages: jest.fn().mockResolvedValue([]),
33
- extractStructuredData: jest.fn().mockResolvedValue(null),
34
- createPage: jest.fn(),
35
- updatePage: jest.fn(),
36
- deletePage: overrides.deletePage ?? jest.fn().mockResolvedValue(undefined),
37
- getPageVersion: jest.fn(),
38
- } as unknown as ContentAdapter;
39
- }
40
-
41
- const VALID_INPUT = {
42
- type: "Product" as const,
43
- slug: "widget-pro",
44
- ifMatch: "v1",
45
- };
46
-
47
- describe("ailkDeletePage", () => {
48
- beforeEach(() => {
49
- mockCheckLicense.mockReset();
50
- });
51
-
52
- it("returns payment_required for free tier; adapter not called", async () => {
53
- mockCheckLicense.mockResolvedValue({
54
- tier: "free",
55
- plan: null,
56
- features: [],
57
- expiresAt: null,
58
- revoked: false,
59
- });
60
- const adapter = makeAdapter();
61
-
62
- const result = await ailkDeletePage(VALID_INPUT, adapter);
63
-
64
- expect(result).toMatchObject({ error: "payment_required" });
65
- expect(adapter.deletePage).not.toHaveBeenCalled();
66
- });
67
-
68
- it("returns validation_failed when slug violates SLUG_PATTERN (issue #398 — defence-in-depth parity with create/update)", async () => {
69
- mockCheckLicense.mockResolvedValue({
70
- tier: "pro",
71
- plan: "pro",
72
- features: [],
73
- expiresAt: null,
74
- revoked: false,
75
- });
76
- const adapter = makeAdapter();
77
-
78
- const result = await ailkDeletePage(
79
- { ...VALID_INPUT, slug: "../etc/passwd" },
80
- adapter,
81
- );
82
-
83
- expect(result).toMatchObject({ error: "validation_failed" });
84
- expect(adapter.deletePage).not.toHaveBeenCalled();
85
- });
86
-
87
- it("returns validation_failed when ifMatch is missing", async () => {
88
- mockCheckLicense.mockResolvedValue({
89
- tier: "pro",
90
- plan: "pro",
91
- features: [],
92
- expiresAt: null,
93
- revoked: false,
94
- });
95
- const adapter = makeAdapter();
96
- const { ifMatch: _ignored, ...inputNoIfMatch } = VALID_INPUT;
97
-
98
- const result = await ailkDeletePage(
99
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
- inputNoIfMatch as any,
101
- adapter,
102
- );
103
-
104
- expect(result).toMatchObject({ error: "validation_failed" });
105
- expect(adapter.deletePage).not.toHaveBeenCalled();
106
- });
107
-
108
- it("returns conflict on stale ifMatch", async () => {
109
- mockCheckLicense.mockResolvedValue({
110
- tier: "pro",
111
- plan: "pro",
112
- features: [],
113
- expiresAt: null,
114
- revoked: false,
115
- });
116
- const adapter = makeAdapter({
117
- deletePage: jest
118
- .fn()
119
- .mockRejectedValue(
120
- new AdapterWriteError(
121
- "conflict",
122
- "Version mismatch on Product/widget-pro.mdx",
123
- { currentVersion: "v9" },
124
- ),
125
- ),
126
- });
127
-
128
- const result = await ailkDeletePage(VALID_INPUT, adapter);
129
-
130
- expect(result).toMatchObject({
131
- error: "conflict",
132
- details: { currentVersion: "v9" },
133
- });
134
- });
135
-
136
- it("returns not_found when adapter signals missing slug", async () => {
137
- mockCheckLicense.mockResolvedValue({
138
- tier: "pro",
139
- plan: "pro",
140
- features: [],
141
- expiresAt: null,
142
- revoked: false,
143
- });
144
- const adapter = makeAdapter({
145
- deletePage: jest
146
- .fn()
147
- .mockRejectedValue(
148
- new AdapterWriteError(
149
- "not_found",
150
- "No page at Product/widget-pro.mdx",
151
- ),
152
- ),
153
- });
154
-
155
- const result = await ailkDeletePage(VALID_INPUT, adapter);
156
-
157
- expect(result).toMatchObject({ error: "not_found" });
158
- });
159
-
160
- it("returns { ok: true } on happy path", async () => {
161
- mockCheckLicense.mockResolvedValue({
162
- tier: "pro",
163
- plan: "pro",
164
- features: [],
165
- expiresAt: null,
166
- revoked: false,
167
- });
168
- const adapter = makeAdapter();
169
-
170
- const result = await ailkDeletePage(VALID_INPUT, adapter);
171
-
172
- expect(result).toEqual({ ok: true });
173
- expect(adapter.deletePage).toHaveBeenCalledWith("Product", "widget-pro", {
174
- ifMatch: "v1",
175
- });
176
- });
177
- });
@@ -1,196 +0,0 @@
1
- // oss: false
2
- /**
3
- * Tests for the update_page MCP tool handler.
4
- *
5
- * D14a Stage 1 Unit C — Layer 1 / Layer 2 acceptance criteria mapped here:
6
- * - Free tier → payment_required, no adapter call.
7
- * - Missing ifMatch → validation_failed (caught by input schema).
8
- * - Stale ifMatch → conflict body with details.currentVersion echoed.
9
- * - Slug not found → not_found body.
10
- * - Bad frontmatter → validation_failed, no adapter call.
11
- * - Pro + valid + matching ifMatch → { page, version } success body.
12
- */
13
-
14
- import { AdapterWriteError, type ContentAdapter } from "@working-theory/content-adapters";
15
-
16
- const mockCheckLicense = jest.fn();
17
-
18
- jest.mock("@working-theory/license", () => ({
19
- checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
20
- }));
21
-
22
- import { ailkUpdatePage } from "../src/tools/update_page.js";
23
-
24
- function makeAdapter(
25
- overrides: Partial<{
26
- updatePage: jest.Mock;
27
- }> = {},
28
- ): ContentAdapter {
29
- // eslint-disable-next-line no-restricted-syntax -- #506/#507: pre-existing test-hygiene violation
30
- return {
31
- getPageBySlug: jest.fn().mockResolvedValue(null),
32
- listPages: jest.fn().mockResolvedValue([]),
33
- getDetailBySlug: jest.fn().mockResolvedValue(null),
34
- listDetail: jest.fn().mockResolvedValue([]),
35
- searchPages: jest.fn().mockResolvedValue([]),
36
- extractStructuredData: jest.fn().mockResolvedValue(null),
37
- createPage: jest.fn(),
38
- updatePage: overrides.updatePage ?? jest.fn(),
39
- deletePage: jest.fn(),
40
- getPageVersion: jest.fn(),
41
- } as unknown as ContentAdapter;
42
- }
43
-
44
- const VALID_INPUT = {
45
- type: "Product" as const,
46
- slug: "widget-pro",
47
- page: {
48
- title: "Widget Pro v2",
49
- description: "Updated.",
50
- },
51
- body: "# Widget Pro v2\n",
52
- ifMatch: "v1",
53
- };
54
-
55
- describe("ailkUpdatePage", () => {
56
- beforeEach(() => {
57
- mockCheckLicense.mockReset();
58
- });
59
-
60
- it("returns payment_required for free tier; adapter not called", async () => {
61
- mockCheckLicense.mockResolvedValue({
62
- tier: "free",
63
- plan: null,
64
- features: [],
65
- expiresAt: null,
66
- revoked: false,
67
- });
68
- const adapter = makeAdapter();
69
-
70
- const result = await ailkUpdatePage(VALID_INPUT, adapter);
71
-
72
- expect(result).toMatchObject({ error: "payment_required" });
73
- expect(adapter.updatePage).not.toHaveBeenCalled();
74
- });
75
-
76
- it("returns validation_failed when ifMatch is missing", async () => {
77
- mockCheckLicense.mockResolvedValue({
78
- tier: "pro",
79
- plan: "pro",
80
- features: [],
81
- expiresAt: null,
82
- revoked: false,
83
- });
84
- const adapter = makeAdapter();
85
- const { ifMatch: _ignored, ...inputNoIfMatch } = VALID_INPUT;
86
-
87
- const result = await ailkUpdatePage(
88
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
- inputNoIfMatch as any,
90
- adapter,
91
- );
92
-
93
- expect(result).toMatchObject({ error: "validation_failed" });
94
- expect(adapter.updatePage).not.toHaveBeenCalled();
95
- });
96
-
97
- it("returns conflict with details.currentVersion on stale ifMatch", async () => {
98
- mockCheckLicense.mockResolvedValue({
99
- tier: "pro",
100
- plan: "pro",
101
- features: [],
102
- expiresAt: null,
103
- revoked: false,
104
- });
105
- const adapter = makeAdapter({
106
- updatePage: jest
107
- .fn()
108
- .mockRejectedValue(
109
- new AdapterWriteError(
110
- "conflict",
111
- "Version mismatch on Product/widget-pro.mdx",
112
- { currentVersion: "v9" },
113
- ),
114
- ),
115
- });
116
-
117
- const result = await ailkUpdatePage(VALID_INPUT, adapter);
118
-
119
- expect(result).toMatchObject({
120
- error: "conflict",
121
- details: { currentVersion: "v9" },
122
- });
123
- });
124
-
125
- it("returns not_found when adapter signals missing slug", async () => {
126
- mockCheckLicense.mockResolvedValue({
127
- tier: "pro",
128
- plan: "pro",
129
- features: [],
130
- expiresAt: null,
131
- revoked: false,
132
- });
133
- const adapter = makeAdapter({
134
- updatePage: jest
135
- .fn()
136
- .mockRejectedValue(
137
- new AdapterWriteError(
138
- "not_found",
139
- "No page at Product/widget-pro.mdx",
140
- ),
141
- ),
142
- });
143
-
144
- const result = await ailkUpdatePage(VALID_INPUT, adapter);
145
-
146
- expect(result).toMatchObject({ error: "not_found" });
147
- });
148
-
149
- it("returns validation_failed when frontmatter is bad", async () => {
150
- mockCheckLicense.mockResolvedValue({
151
- tier: "pro",
152
- plan: "pro",
153
- features: [],
154
- expiresAt: null,
155
- revoked: false,
156
- });
157
- const adapter = makeAdapter();
158
-
159
- const result = await ailkUpdatePage(
160
- { ...VALID_INPUT, page: { title: "Missing description" } },
161
- adapter,
162
- );
163
-
164
- expect(result).toMatchObject({ error: "validation_failed" });
165
- expect(adapter.updatePage).not.toHaveBeenCalled();
166
- });
167
-
168
- it("returns { page, version } on happy path", async () => {
169
- mockCheckLicense.mockResolvedValue({
170
- tier: "pro",
171
- plan: "pro",
172
- features: [],
173
- expiresAt: null,
174
- revoked: false,
175
- });
176
- const persistedPage = {
177
- type: "Product" as const,
178
- slug: "widget-pro",
179
- title: "Widget Pro v2",
180
- description: "Updated.",
181
- };
182
- const adapter = makeAdapter({
183
- updatePage: jest
184
- .fn()
185
- .mockResolvedValue({ page: persistedPage, version: "v2" }),
186
- });
187
-
188
- const result = await ailkUpdatePage(VALID_INPUT, adapter);
189
-
190
- expect(result).toEqual({ page: persistedPage, version: "v2" });
191
- const callArgs = (adapter.updatePage as jest.Mock).mock.calls[0];
192
- expect(callArgs[0]).toBe("Product");
193
- expect(callArgs[1]).toBe("widget-pro");
194
- expect(callArgs[3]).toEqual({ ifMatch: "v1" });
195
- });
196
- });