create-ailk 0.21.0 → 0.23.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.
- package/component-catalog.json +186 -8
- package/component-catalog.md +9 -7
- package/dist/add-module.js +20 -14
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +294 -12
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +12 -3
- package/dist/derive-architecture.js +20 -8
- package/dist/design-package.js +7 -2
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +5 -5
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +3 -2
- package/dist/lib/scaffold-render-sweep.js +5 -8
- package/dist/module-architecture.js +99 -9
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/programmatic.js +19 -5
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/site-chrome.d.ts +8 -0
- package/dist/site-chrome.js +17 -1
- package/dist/spoke-checkout.d.ts +65 -41
- package/dist/spoke-checkout.js +208 -430
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +52 -26
- package/dist/sync-routes.d.ts +38 -1
- package/dist/sync-routes.js +187 -13
- package/dist/template-record.d.ts +4 -3
- package/dist/template-record.js +8 -4
- package/dist/topology.js +25 -1
- package/dist/update.js +15 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/designs/minimalism/DESIGN.md +1 -1
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +9 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +27 -0
- package/templates/apps/api/CLAUDE.md +87 -17
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/consent-store.ts +28 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
- package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
- package/templates/apps/api/src/lib/stripe.ts +74 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/auth.ts +13 -6
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/spec.ts +33 -3
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +268 -0
- package/templates/apps/api/src/routes/consent/index.ts +15 -7
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/get-page.ts +4 -0
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
- package/templates/apps/api/src/routes/leads/index.ts +164 -7
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +245 -36
- package/templates/apps/api/src/server.ts +163 -46
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1383 -133
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
- package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
- package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
- package/templates/apps/api/src/services/consent.ts +95 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +431 -47
- package/templates/apps/api/src/services/magnet-completion.ts +242 -0
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +164 -17
- package/templates/apps/api/src/services/plan-offerings.ts +125 -0
- package/templates/apps/api/src/services/public-site-resolver.ts +27 -15
- package/templates/apps/mcp/CLAUDE.md +22 -14
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +24 -31
- package/templates/apps/web/CLAUDE.md +393 -9
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +295 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +120 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +23 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +319 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +141 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +424 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +72 -11
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
- package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
- package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +160 -13
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +466 -36
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +172 -57
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -60
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +341 -4
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
- package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
- package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/auth.ts +28 -2
- package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +7 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +101 -25
- package/templates/apps/web/lib/account-invite.ts +37 -0
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +16 -0
- package/templates/apps/web/lib/consent-policies.ts +22 -0
- package/templates/apps/web/lib/deck-page.ts +7 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +42 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +202 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +449 -0
- package/templates/apps/web/lib/site-chrome.ts +2 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +71 -20
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/package.json +1 -0
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
- package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
- package/templates/apps/web/src/lib/lead-write.ts +90 -0
- package/templates/apps/web/src/lib/rate-limit.ts +79 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +69 -0
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
- package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
- package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +64 -1
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/inbox/schema.prisma +17 -2
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +62 -3
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign-up agreements: their evidence, and the account-level reading
|
|
3
|
+
* (#8765, spec docs/specs/8765-sign-up-refuses-without-the-spec.md D2, D5).
|
|
4
|
+
*
|
|
5
|
+
* GET /v1/consent/sign-up?locale=
|
|
6
|
+
* Anonymous. `{ workspaceId, agreements: [{ purposeId,
|
|
7
|
+
* policyVersion, locale, statement, statementHash }] }` — one entry per
|
|
8
|
+
* `signUp` agreement of the site registry, its statement resolved by
|
|
9
|
+
* `resolveConsentStatement` for `locale` (default `en`) and hashed by
|
|
10
|
+
* `consentStatementHash`: the same evidence `recordPersonConsent`
|
|
11
|
+
* stamps, from the same functions. `Cache-Control: no-store`.
|
|
12
|
+
* GET /v1/consent/account?locale=
|
|
13
|
+
* The caller's sign-up agreements: `{ agreements: [{ id, statement,
|
|
14
|
+
* policyVersion, acceptedVersion, acceptedAt, current }], complete }`,
|
|
15
|
+
* folded from the caller's `user`-subject rows (`userPlatformRows`).
|
|
16
|
+
* `acceptedVersion`/`acceptedAt` come from the newest row when it is a
|
|
17
|
+
* grant, else `null`; `current` is a grant at the registry's version;
|
|
18
|
+
* `complete` is every entry current.
|
|
19
|
+
* POST /v1/consent/account
|
|
20
|
+
* `{ purposeIds, locale? }` — every id must be a `signUp` agreement
|
|
21
|
+
* (`404 purpose_unknown` with NOTHING written, all ids checked before
|
|
22
|
+
* any write); each is written through `recordPersonConsent` against
|
|
23
|
+
* subject `user` / the caller, no Person, source `account_settings`,
|
|
24
|
+
* with `sourceRef: spoke:<id>` for a hub credential (SEC-8765-A). A
|
|
25
|
+
* grant already at the current version adds nothing. Answers the GET
|
|
26
|
+
* body.
|
|
27
|
+
*
|
|
28
|
+
* The agreement belongs to the User account, a login shared by every site
|
|
29
|
+
* that signs in through this hub (C3), so the workspace is ALWAYS
|
|
30
|
+
* `resolveSiteWorkspaceId()` — the deployment's own site, where its `User`
|
|
31
|
+
* rows live — never a key, body or query value; `null` is
|
|
32
|
+
* `503 tenant_unresolved`. One row there answers the hub and every spoke.
|
|
33
|
+
*
|
|
34
|
+
* Callers. The evidence route: the web's own server, stamping the grant it
|
|
35
|
+
* writes inside Better Auth's sign-up transaction (`apps/web/lib/
|
|
36
|
+
* sign-up-agreements.ts`, D3, D4). Registered at the top level in its own
|
|
37
|
+
* rate-limited scope (`signupevidence`), outside every module gate; a call
|
|
38
|
+
* presenting a valid site key is counted per key there, any other per IP
|
|
39
|
+
* (#8805, `server.ts`) — the key never chooses the workspace. The
|
|
40
|
+
* account pair: `memberCaller` — the hub's own cookie session or a hub
|
|
41
|
+
* credential (the account layout's interstitial, D7); a personal `ApiToken`
|
|
42
|
+
* is `401 unauthorized`. There is no verified-email floor: the agreement is
|
|
43
|
+
* about the `User` row, and no Person is read or created. Registered in
|
|
44
|
+
* `authScope` unconditionally (auth and consent are never-gate surfaces),
|
|
45
|
+
* and both are in `HUB_SESSION_ROUTES`. Logs `{ event: "account-agreements",
|
|
46
|
+
* outcome }` only.
|
|
47
|
+
*
|
|
48
|
+
* MCP parity note: no MCP twin — the evidence route is part of the sign-up
|
|
49
|
+
* flow and the account pair is identity-bound; the anonymous MCP surface is
|
|
50
|
+
* identity-blind (tests/parity/README.md).
|
|
51
|
+
*/
|
|
52
|
+
import {
|
|
53
|
+
accountAgreementsBodySchema,
|
|
54
|
+
accountAgreementsQuerySchema,
|
|
55
|
+
accountAgreementsResponseSchema,
|
|
56
|
+
consentSignUpQuerySchema,
|
|
57
|
+
consentSignUpResponseSchema,
|
|
58
|
+
resolveConsentStatement,
|
|
59
|
+
signUpAgreements,
|
|
60
|
+
type AccountAgreementsResponse,
|
|
61
|
+
type SiteConsentConfig,
|
|
62
|
+
} from "@working-theory/validation";
|
|
63
|
+
import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
|
|
64
|
+
import type { z } from "zod";
|
|
65
|
+
|
|
66
|
+
import { userPlatformRows } from "../../lib/consent-store.js";
|
|
67
|
+
import { resolveSiteWorkspaceId } from "../../lib/site.js";
|
|
68
|
+
import { memberCaller } from "../../middleware/auth.js";
|
|
69
|
+
import {
|
|
70
|
+
consentConfigForSite,
|
|
71
|
+
consentStatementHash,
|
|
72
|
+
recordPersonConsent,
|
|
73
|
+
} from "../../services/consent.js";
|
|
74
|
+
|
|
75
|
+
const EVENT = "account-agreements";
|
|
76
|
+
|
|
77
|
+
function refuse(
|
|
78
|
+
reply: FastifyReply,
|
|
79
|
+
status: number,
|
|
80
|
+
code: string,
|
|
81
|
+
message: string,
|
|
82
|
+
) {
|
|
83
|
+
return reply.status(status).send({ ok: false, error: { code, message } });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function send<T extends z.ZodTypeAny>(
|
|
87
|
+
reply: FastifyReply,
|
|
88
|
+
schema: T,
|
|
89
|
+
data: unknown,
|
|
90
|
+
) {
|
|
91
|
+
const parsed = schema.safeParse(data);
|
|
92
|
+
if (!parsed.success) {
|
|
93
|
+
return refuse(
|
|
94
|
+
reply,
|
|
95
|
+
500,
|
|
96
|
+
"internal_error",
|
|
97
|
+
"Response failed contract validation",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return reply.send({ ok: true, data: parsed.data });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function log(request: FastifyRequest, outcome: string): void {
|
|
104
|
+
if (outcome === "saved" || outcome === "unchanged") {
|
|
105
|
+
request.log.info({ event: EVENT, outcome }, "[consent] account agreements");
|
|
106
|
+
} else {
|
|
107
|
+
request.log.warn({ event: EVENT, outcome }, "[consent] refused");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function refuseUnresolved(request: FastifyRequest, reply: FastifyReply) {
|
|
112
|
+
log(request, "tenant_unresolved");
|
|
113
|
+
return refuse(
|
|
114
|
+
reply,
|
|
115
|
+
503,
|
|
116
|
+
"tenant_unresolved",
|
|
117
|
+
"This site cannot take this request.",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The caller's sign-up agreements in the site workspace (D5). */
|
|
122
|
+
async function accountReading(
|
|
123
|
+
workspaceId: string,
|
|
124
|
+
userId: string,
|
|
125
|
+
config: SiteConsentConfig,
|
|
126
|
+
locale: string,
|
|
127
|
+
): Promise<AccountAgreementsResponse> {
|
|
128
|
+
const rows = await userPlatformRows(workspaceId, userId);
|
|
129
|
+
const newest = new Map(rows.map((row) => [row.purposeId, row]));
|
|
130
|
+
const agreements = signUpAgreements(config).map((purpose) => {
|
|
131
|
+
const row = newest.get(purpose.id);
|
|
132
|
+
const grant = row?.granted === true ? row : null;
|
|
133
|
+
return {
|
|
134
|
+
id: purpose.id,
|
|
135
|
+
statement: resolveConsentStatement(purpose, locale).statement,
|
|
136
|
+
policyVersion: purpose.policyVersion,
|
|
137
|
+
acceptedVersion: grant?.policyVersion ?? null,
|
|
138
|
+
acceptedAt: grant === null ? null : grant.at.toISOString(),
|
|
139
|
+
current: grant !== null && grant.policyVersion === purpose.policyVersion,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
return { agreements, complete: agreements.every((a) => a.current) };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** `GET /v1/consent/sign-up` — top level, its own rate-limited scope (D2). */
|
|
146
|
+
export const consentSignUpRoute: FastifyPluginAsync = async (fastify) => {
|
|
147
|
+
fastify.get("/v1/consent/sign-up", async (request, reply) => {
|
|
148
|
+
reply.header("Cache-Control", "no-store");
|
|
149
|
+
const query = consentSignUpQuerySchema.safeParse(request.query);
|
|
150
|
+
if (!query.success) {
|
|
151
|
+
return refuse(reply, 400, "invalid_input", "The request was not valid.");
|
|
152
|
+
}
|
|
153
|
+
const locale = query.data.locale ?? "en";
|
|
154
|
+
|
|
155
|
+
const workspaceId = await resolveSiteWorkspaceId();
|
|
156
|
+
if (workspaceId === null) return refuseUnresolved(request, reply);
|
|
157
|
+
|
|
158
|
+
const config = await consentConfigForSite(workspaceId);
|
|
159
|
+
const agreements = signUpAgreements(config).map((purpose) => {
|
|
160
|
+
const resolved = resolveConsentStatement(purpose, locale);
|
|
161
|
+
return {
|
|
162
|
+
purposeId: purpose.id,
|
|
163
|
+
policyVersion: purpose.policyVersion,
|
|
164
|
+
locale: resolved.locale,
|
|
165
|
+
statement: resolved.statement,
|
|
166
|
+
statementHash: consentStatementHash(resolved.statement),
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
return send(reply, consentSignUpResponseSchema, {
|
|
170
|
+
workspaceId,
|
|
171
|
+
agreements,
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/** `GET`/`POST /v1/consent/account` — in `authScope`, unconditionally (D5). */
|
|
177
|
+
export const consentAccountRoutes: FastifyPluginAsync = async (fastify) => {
|
|
178
|
+
fastify.get("/v1/consent/account", async (request, reply) => {
|
|
179
|
+
const userId = memberCaller(request);
|
|
180
|
+
if (userId === null) {
|
|
181
|
+
log(request, "unauthorized");
|
|
182
|
+
return reply.status(401).send({ error: "unauthorized" });
|
|
183
|
+
}
|
|
184
|
+
const query = accountAgreementsQuerySchema.safeParse(request.query);
|
|
185
|
+
if (!query.success) {
|
|
186
|
+
log(request, "invalid_input");
|
|
187
|
+
return refuse(reply, 400, "invalid_input", "The request was not valid.");
|
|
188
|
+
}
|
|
189
|
+
const workspaceId = await resolveSiteWorkspaceId();
|
|
190
|
+
if (workspaceId === null) return refuseUnresolved(request, reply);
|
|
191
|
+
|
|
192
|
+
const config = await consentConfigForSite(workspaceId);
|
|
193
|
+
return send(
|
|
194
|
+
reply,
|
|
195
|
+
accountAgreementsResponseSchema,
|
|
196
|
+
await accountReading(
|
|
197
|
+
workspaceId,
|
|
198
|
+
userId,
|
|
199
|
+
config,
|
|
200
|
+
query.data.locale ?? "en",
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
fastify.post("/v1/consent/account", async (request, reply) => {
|
|
206
|
+
const userId = memberCaller(request);
|
|
207
|
+
if (userId === null) {
|
|
208
|
+
log(request, "unauthorized");
|
|
209
|
+
return reply.status(401).send({ error: "unauthorized" });
|
|
210
|
+
}
|
|
211
|
+
const body = accountAgreementsBodySchema.safeParse(request.body);
|
|
212
|
+
if (!body.success) {
|
|
213
|
+
log(request, "invalid_input");
|
|
214
|
+
return refuse(reply, 400, "invalid_input", "The request was not valid.");
|
|
215
|
+
}
|
|
216
|
+
const locale = body.data.locale ?? "en";
|
|
217
|
+
const workspaceId = await resolveSiteWorkspaceId();
|
|
218
|
+
if (workspaceId === null) return refuseUnresolved(request, reply);
|
|
219
|
+
|
|
220
|
+
const config = await consentConfigForSite(workspaceId);
|
|
221
|
+
// Every id is checked before any write, so a refusal writes nothing.
|
|
222
|
+
const agreementIds = new Set(signUpAgreements(config).map((p) => p.id));
|
|
223
|
+
const purposeIds = [...new Set(body.data.purposeIds)];
|
|
224
|
+
if (!purposeIds.every((id) => agreementIds.has(id))) {
|
|
225
|
+
log(request, "purpose_unknown");
|
|
226
|
+
return refuse(
|
|
227
|
+
reply,
|
|
228
|
+
404,
|
|
229
|
+
"purpose_unknown",
|
|
230
|
+
"That agreement can't be accepted here.",
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let changed = false;
|
|
235
|
+
for (const purposeId of purposeIds) {
|
|
236
|
+
const result = await recordPersonConsent({
|
|
237
|
+
workspaceId,
|
|
238
|
+
personId: null,
|
|
239
|
+
purposeId,
|
|
240
|
+
granted: true,
|
|
241
|
+
source: "account_settings",
|
|
242
|
+
...(request.hubSession !== undefined && {
|
|
243
|
+
sourceRef: `spoke:${request.hubSession.spokeId}`,
|
|
244
|
+
}),
|
|
245
|
+
locale,
|
|
246
|
+
config,
|
|
247
|
+
subject: { subjectType: "user", subjectId: userId },
|
|
248
|
+
});
|
|
249
|
+
if (!result.ok) {
|
|
250
|
+
log(request, result.code);
|
|
251
|
+
return refuse(
|
|
252
|
+
reply,
|
|
253
|
+
result.code === "purpose_unknown" ? 404 : 400,
|
|
254
|
+
result.code,
|
|
255
|
+
"That agreement can't be accepted here.",
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
changed ||= result.changed;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
log(request, changed ? "saved" : "unchanged");
|
|
262
|
+
return send(
|
|
263
|
+
reply,
|
|
264
|
+
accountAgreementsResponseSchema,
|
|
265
|
+
await accountReading(workspaceId, userId, config, locale),
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
};
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The consent routes (#8438, spec
|
|
3
|
-
* docs/specs/8438-consent-registry-and-ledger-step-spec.md D5, D8, D9
|
|
3
|
+
* docs/specs/8438-consent-registry-and-ledger-step-spec.md D5, D8, D9; #8765,
|
|
4
|
+
* spec docs/specs/8765-sign-up-refuses-without-the-spec.md D2, D5).
|
|
4
5
|
*
|
|
5
6
|
* cookie.ts POST /v1/consent/cookie — the banner's log
|
|
6
7
|
* unsubscribe.ts POST /v1/consent/unsubscribe — a signed link's landing
|
|
8
|
+
* account.ts GET /v1/consent/sign-up — the sign-up agreements'
|
|
9
|
+
* evidence (anonymous)
|
|
10
|
+
* GET/POST /v1/consent/account — the caller's account-level
|
|
11
|
+
* sign-up agreements (cookie session or hub credential)
|
|
7
12
|
* platform.ts GET/POST /v1/consent/platform — the Admin owner's
|
|
8
13
|
* consents to Working Theory (multi-tenant module only)
|
|
9
14
|
*
|
|
10
|
-
* The first two are the never-gated
|
|
11
|
-
* level, each in its OWN rate-limited scope (`consent`,
|
|
12
|
-
* filling one never blocks
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* The first two and the evidence route are the never-gated floor: registered
|
|
16
|
+
* at the top level, each in its OWN rate-limited scope (`consent`,
|
|
17
|
+
* `unsubscribe`, `signupevidence`) so filling one never blocks another; the
|
|
18
|
+
* first two are also on the forwarded-visitor route list
|
|
19
|
+
* (`middleware/rate-limit.ts`). The account pair registers in `authScope`,
|
|
20
|
+
* unconditionally. This barrel re-exports those four plugins; the platform
|
|
21
|
+
* routes belong to the multi-tenant slice and are loaded only through
|
|
22
|
+
* `server.ts`'s slice table.
|
|
16
23
|
*/
|
|
24
|
+
export { consentAccountRoutes, consentSignUpRoute } from "./account.js";
|
|
17
25
|
export { consentCookieRoute } from "./cookie.js";
|
|
18
26
|
export { consentUnsubscribeRoute } from "./unsubscribe.js";
|
|
@@ -206,6 +206,42 @@ describe("GET /api/content/:type/:slug", () => {
|
|
|
206
206
|
* established for #5831. `get-page.ts` itself is unedited (N3) — it
|
|
207
207
|
* already accepted both types via `SCHEMA_ORG_TYPES` before this issue.
|
|
208
208
|
*/
|
|
209
|
+
// #8746 D2, AC-1 — the site origin (`_site.mdx` `baseUrl`) reaches the
|
|
210
|
+
// JobPosting builder, so the posting names where to apply.
|
|
211
|
+
describe("a JobPosting carries its ApplyAction when the site is known (#8746 AC-1)", () => {
|
|
212
|
+
const writePosting = () =>
|
|
213
|
+
fixture.writeFixturePage(
|
|
214
|
+
"JobPosting",
|
|
215
|
+
"content-strategist",
|
|
216
|
+
{
|
|
217
|
+
type: "JobPosting",
|
|
218
|
+
slug: "content-strategist",
|
|
219
|
+
title: "Content Strategist",
|
|
220
|
+
description: "Own the content pipeline.",
|
|
221
|
+
},
|
|
222
|
+
"# Content Strategist",
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
it("targets <baseUrl>/<locale>/careers/<slug>/apply", async () => {
|
|
226
|
+
writeSiteMdx(fixture.getContentRoot());
|
|
227
|
+
await writePosting();
|
|
228
|
+
const res = await app.inject({ method: "GET", url: "/v1/content/JobPosting/content-strategist" });
|
|
229
|
+
expect(res.statusCode).toBe(200);
|
|
230
|
+
const entity = JSON.parse(res.body).mainEntity as Record<string, unknown>;
|
|
231
|
+
expect(entity.potentialAction).toEqual({
|
|
232
|
+
"@type": "ApplyAction",
|
|
233
|
+
target: "https://example.com/en/careers/content-strategist/apply",
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("emits no ApplyAction when the deployment has no _site.mdx", async () => {
|
|
238
|
+
await writePosting();
|
|
239
|
+
const res = await app.inject({ method: "GET", url: "/v1/content/JobPosting/content-strategist" });
|
|
240
|
+
expect(res.statusCode).toBe(200);
|
|
241
|
+
expect("potentialAction" in (JSON.parse(res.body).mainEntity as Record<string, unknown>)).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
209
245
|
describe("parity with the adapter the MCP tool calls (D3)", () => {
|
|
210
246
|
it.each(["BlogPosting", "DocsDetail"] as const)(
|
|
211
247
|
"GET /v1/content/%s/:slug?include=body deep-equals adapter.getPageSource",
|