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
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"use server";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* subscribeNewsletterAction — Server Action paired with the
|
|
5
|
-
* `ailkSubscribeNewsletter` MCP tool (#6730 D9).
|
|
6
|
-
*
|
|
7
|
-
* `/newsletter` renders the shared `LeadCaptureForm` bound to this action
|
|
8
|
-
* when the API accepts a subscription (a database is set up) and the page's
|
|
9
|
-
* content declares no signup section of its own. Validates the address with
|
|
10
|
-
* the tool's own input schema, calls `POST /v1/leads/subscribe` through
|
|
11
|
-
* `@working-theory/api-client`, and maps the outcome the same way
|
|
12
|
-
* `captureLeadAction` does: the API's 503 `lead_capture_not_configured` →
|
|
13
|
-
* `unavailable`; any other non-2xx or thrown error → the generic `error`;
|
|
14
|
-
* only a 2xx is `success`.
|
|
15
|
-
*
|
|
16
|
-
* Visitor forwarding (#7394): forwards the visitor exactly as
|
|
17
|
-
* `captureLeadAction` does, through `visitorForwardHeaders` and the
|
|
18
|
-
* server-only `AILK_VISITOR_FORWARD_KEY`; without them the call is made
|
|
19
|
-
* with one argument, as before.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { leads, visitorForwardHeaders } from "@working-theory/api-client";
|
|
23
|
-
import { LEAD_CAPTURE_HONEYPOT, type LeadCaptureFormState } from "@working-theory/ui";
|
|
24
|
-
import { subscribeNewsletterInputSchema } from "@working-theory/validation";
|
|
25
|
-
import { cookies, headers } from "next/headers";
|
|
26
|
-
|
|
27
|
-
import { consentVisitorHeader, readConsentVisitor } from "../../../lib/consent-visitor";
|
|
28
|
-
|
|
29
|
-
const GENERIC_ERROR = "Something went wrong, please try again.";
|
|
30
|
-
|
|
31
|
-
export async function subscribeNewsletterAction(
|
|
32
|
-
_prevState: LeadCaptureFormState,
|
|
33
|
-
formData: FormData,
|
|
34
|
-
): Promise<LeadCaptureFormState> {
|
|
35
|
-
// Honeypot: a bot that fills the hidden field gets a person's answer, and
|
|
36
|
-
// nothing is sent.
|
|
37
|
-
const honeypot = formData.get(LEAD_CAPTURE_HONEYPOT);
|
|
38
|
-
if (typeof honeypot === "string" && honeypot !== "") return { kind: "success" };
|
|
39
|
-
|
|
40
|
-
const parsed = subscribeNewsletterInputSchema.safeParse({
|
|
41
|
-
email: formData.get("email"),
|
|
42
|
-
});
|
|
43
|
-
if (!parsed.success) {
|
|
44
|
-
return {
|
|
45
|
-
kind: "error",
|
|
46
|
-
fieldErrors: { email: parsed.error.issues[0]?.message ?? "Invalid email" },
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const siteId = process.env.AILK_SITE_ID ?? "default";
|
|
51
|
-
const body = { siteId, source: "newsletter", email: parsed.data.email };
|
|
52
|
-
let init: { headers: Record<string, string> } | undefined;
|
|
53
|
-
try {
|
|
54
|
-
const forwarded = visitorForwardHeaders(
|
|
55
|
-
await headers(),
|
|
56
|
-
process.env.AILK_VISITOR_FORWARD_KEY,
|
|
57
|
-
);
|
|
58
|
-
if (forwarded) init = { headers: forwarded };
|
|
59
|
-
} catch {
|
|
60
|
-
// No request scope to read — subscribe without forwarding.
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// #8438 D12 — the banner's visitor id, so a later subscribe links its
|
|
64
|
-
// cookie choices to the client record. Absent or invalid → no header.
|
|
65
|
-
try {
|
|
66
|
-
const visitorId = readConsentVisitor(await cookies());
|
|
67
|
-
if (visitorId) init = { headers: { ...(init?.headers ?? {}), ...consentVisitorHeader(visitorId) } };
|
|
68
|
-
} catch {
|
|
69
|
-
// No request scope to read — subscribe with no visitor-link header.
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const res = init
|
|
74
|
-
? await leads.subscribe(body, init)
|
|
75
|
-
: await leads.subscribe(body);
|
|
76
|
-
if (!res.ok) {
|
|
77
|
-
const error =
|
|
78
|
-
typeof res.body === "object" && res.body !== null
|
|
79
|
-
? (res.body as { error?: unknown }).error
|
|
80
|
-
: undefined;
|
|
81
|
-
if (res.status === 503 && error === "lead_capture_not_configured") {
|
|
82
|
-
return { kind: "unavailable" };
|
|
83
|
-
}
|
|
84
|
-
return { kind: "error", message: GENERIC_ERROR };
|
|
85
|
-
}
|
|
86
|
-
return { kind: "success" };
|
|
87
|
-
} catch (err) {
|
|
88
|
-
// The api-client never attaches the request payload to a thrown error,
|
|
89
|
-
// so logging `err` carries no subscriber address.
|
|
90
|
-
console.error("[subscribeNewsletterAction]", err);
|
|
91
|
-
return { kind: "error", message: GENERIC_ERROR };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* Tests for enabledMeetingSlugs (#6183 S9b D5) — the D4-resolving read both
|
|
4
|
-
* /meet pages share — and its spoke branch (#7872 D4).
|
|
5
|
-
*
|
|
6
|
-
* The topology seam is mocked: its hub render imports `../auth`, which this
|
|
7
|
-
* suite has no reason to load (`app/openapi.json/__tests__/route.test.ts`'s
|
|
8
|
-
* pattern).
|
|
9
|
-
*/
|
|
10
|
-
const mockMeetingTypes = jest.fn();
|
|
11
|
-
jest.mock("@working-theory/api-client", () => ({
|
|
12
|
-
__esModule: true,
|
|
13
|
-
schedule: { meetingTypes: (...args: unknown[]) => mockMeetingTypes(...args) },
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
const mockTopology: { value: "hub" | "spoke" } = { value: "hub" };
|
|
17
|
-
// eslint-disable-next-line no-restricted-syntax -- #507: the topology is a generated constant (#7426 D5), not a runtime input; the seam IS the boundary between a hub and a spoke build, and there is no env or HTTP surface that selects it.
|
|
18
|
-
jest.mock("../topology.generated", () => ({
|
|
19
|
-
get TOPOLOGY() {
|
|
20
|
-
return mockTopology.value;
|
|
21
|
-
},
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
import { enabledMeetingSlugs } from "../meeting-slugs";
|
|
25
|
-
|
|
26
|
-
describe("enabledMeetingSlugs", () => {
|
|
27
|
-
const originalEnv = process.env["SCHEDULING_CAL_LINK"];
|
|
28
|
-
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
mockMeetingTypes.mockReset();
|
|
31
|
-
mockTopology.value = "hub";
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
afterEach(() => {
|
|
35
|
-
if (originalEnv === undefined) delete process.env["SCHEDULING_CAL_LINK"];
|
|
36
|
-
else process.env["SCHEDULING_CAL_LINK"] = originalEnv;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("passes a real, populated array through unchanged", async () => {
|
|
40
|
-
mockMeetingTypes.mockResolvedValue({
|
|
41
|
-
ok: true,
|
|
42
|
-
body: { slugs: ["intro", "demo"] },
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual(["intro", "demo"]);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("passes a real, intentionally empty array through unchanged (D4 row 2 — never the fallback)", async () => {
|
|
49
|
-
process.env["SCHEDULING_CAL_LINK"] = "acme/intro-call";
|
|
50
|
-
mockMeetingTypes.mockResolvedValue({ ok: true, body: { slugs: [] } });
|
|
51
|
-
|
|
52
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual([]);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("null + SCHEDULING_CAL_LINK set → the derived singleton (D4 row 1)", async () => {
|
|
56
|
-
process.env["SCHEDULING_CAL_LINK"] = "acme/intro-call";
|
|
57
|
-
mockMeetingTypes.mockResolvedValue({ ok: true, body: { slugs: null } });
|
|
58
|
-
|
|
59
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual(["intro-call"]);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("null + SCHEDULING_CAL_LINK unset → [] (no shipped page to protect)", async () => {
|
|
63
|
-
delete process.env["SCHEDULING_CAL_LINK"];
|
|
64
|
-
mockMeetingTypes.mockResolvedValue({ ok: true, body: { slugs: null } });
|
|
65
|
-
|
|
66
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual([]);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("a non-ok API result → [], fail closed — an outage must not open the gate", async () => {
|
|
70
|
-
process.env["SCHEDULING_CAL_LINK"] = "acme/intro-call";
|
|
71
|
-
mockMeetingTypes.mockResolvedValue({ ok: false, status: 500, body: null });
|
|
72
|
-
|
|
73
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual([]);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("null + a MALFORMED SCHEDULING_CAL_LINK slug → [], never a corrupt fallback (D14 parity, security review 2026-09-19)", async () => {
|
|
77
|
-
// A comma in the derived slug would corrupt the batched event-meta
|
|
78
|
-
// `calLinks` join downstream (it splits on comma), misattributing the
|
|
79
|
-
// rest of the list to a foreign owner — MeetingSlugSchema rejects it.
|
|
80
|
-
process.env["SCHEDULING_CAL_LINK"] = "acme/intro,evil";
|
|
81
|
-
mockMeetingTypes.mockResolvedValue({ ok: true, body: { slugs: null } });
|
|
82
|
-
|
|
83
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual([]);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe("enabledMeetingSlugs on a spoke (#7872 D4)", () => {
|
|
88
|
-
const originalEnv = process.env["SCHEDULING_CAL_LINK"];
|
|
89
|
-
|
|
90
|
-
beforeEach(() => {
|
|
91
|
-
mockMeetingTypes.mockReset();
|
|
92
|
-
mockTopology.value = "spoke";
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
afterEach(() => {
|
|
96
|
-
mockTopology.value = "hub";
|
|
97
|
-
if (originalEnv === undefined) delete process.env["SCHEDULING_CAL_LINK"];
|
|
98
|
-
else process.env["SCHEDULING_CAL_LINK"] = originalEnv;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("null + SCHEDULING_CAL_LINK set → [], never the fallback: the hub books only a stored slug", async () => {
|
|
102
|
-
process.env["SCHEDULING_CAL_LINK"] = "acme/intro-call";
|
|
103
|
-
mockMeetingTypes.mockResolvedValue({ ok: true, body: { slugs: null } });
|
|
104
|
-
|
|
105
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual([]);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("passes the workspace's stored list through unchanged", async () => {
|
|
109
|
-
mockMeetingTypes.mockResolvedValue({ ok: true, body: { slugs: ["intro"] } });
|
|
110
|
-
|
|
111
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual(["intro"]);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("a non-ok API result → [], fail closed", async () => {
|
|
115
|
-
process.env["SCHEDULING_CAL_LINK"] = "acme/intro-call";
|
|
116
|
-
mockMeetingTypes.mockResolvedValue({ ok: false, status: 401, body: null });
|
|
117
|
-
|
|
118
|
-
await expect(enabledMeetingSlugs()).resolves.toEqual([]);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* enabledMeetingSlugs — the enabled-meeting-slugs read both `/meet` pages
|
|
4
|
-
* share (#6183 S9b D5).
|
|
5
|
-
*
|
|
6
|
-
* Resolves `GET /v1/schedule/meeting-types`'s three D4 outcomes into ONE
|
|
7
|
-
* array both pages consume identically:
|
|
8
|
-
*
|
|
9
|
-
* - an array is returned as-is (a real, operator-written enabled list —
|
|
10
|
-
* D4 rows 2 and 3, INCLUDING an intentionally empty `[]`);
|
|
11
|
-
* - `null` becomes the D4 row-1 singleton derived from
|
|
12
|
-
* `SCHEDULING_CAL_LINK` — the ONE slug an unconfigured deployment has
|
|
13
|
-
* ever advertised — or `[]` when that env var is unset;
|
|
14
|
-
* - any non-`ok` API result becomes `[]`, fail closed: an API outage
|
|
15
|
-
* must not open the gate.
|
|
16
|
-
*
|
|
17
|
-
* A duplicated inline call in both pages is how the two would drift, and
|
|
18
|
-
* it is also how one of them would forget the fail-closed arm — this is
|
|
19
|
-
* the ONE place that reasoning lives (D5).
|
|
20
|
-
*
|
|
21
|
-
* ON A SPOKE (#7872 D4), `null` means `[]`: the `SCHEDULING_CAL_LINK`
|
|
22
|
-
* fallback is not consulted. The hub books a spoke's slug only when that
|
|
23
|
-
* spoke's workspace has stored it (`spokeMayBook`), so a fallback slug would
|
|
24
|
-
* advertise a meeting whose every booking fails with `event_not_found`. The
|
|
25
|
-
* other arms are the same on both topologies.
|
|
26
|
-
*/
|
|
27
|
-
import { schedule } from "@working-theory/api-client";
|
|
28
|
-
import { MeetingSlugSchema } from "@working-theory/site-config/schema";
|
|
29
|
-
|
|
30
|
-
import { TOPOLOGY } from "./topology.generated";
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The D4 row-1 fallback: the slug half of the env-configured booker's own
|
|
34
|
-
* calLink (`"<owner>/<slug>"`), or `[]` when the env var is unset. This is
|
|
35
|
-
* deliberately the ONLY reader of `SCHEDULING_CAL_LINK`'s SECOND segment —
|
|
36
|
-
* `meet/[slug]/page.tsx`'s own `composedCalLink` reads the FIRST segment
|
|
37
|
-
* (the owner) for an unrelated purpose and is not touched by this file.
|
|
38
|
-
*
|
|
39
|
-
* Re-validated against `MeetingSlugSchema` (D14 parity with the operator-
|
|
40
|
-
* written path — `meeting-types.ts`'s own per-entry `safeParse` filter):
|
|
41
|
-
* `SCHEDULING_CAL_LINK` is operator-set env config, not a schema-validated
|
|
42
|
-
* write, so a malformed value (a stray comma, an empty second segment) could
|
|
43
|
-
* otherwise yield a slug that corrupts the batched `event-meta` `calLinks`
|
|
44
|
-
* join downstream — which splits on comma and would misattribute the rest
|
|
45
|
-
* of the list to a foreign owner (security review, 2026-09-19).
|
|
46
|
-
*/
|
|
47
|
-
function derivedFallbackSlugs(): string[] {
|
|
48
|
-
const configured = process.env["SCHEDULING_CAL_LINK"];
|
|
49
|
-
if (!configured) return [];
|
|
50
|
-
const slug = configured.split("/")[1];
|
|
51
|
-
if (!slug) return [];
|
|
52
|
-
const parsed = MeetingSlugSchema.safeParse(slug);
|
|
53
|
-
return parsed.success ? [parsed.data] : [];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** The enabled meeting-type slug set for this deployment, D4-resolved. */
|
|
57
|
-
export async function enabledMeetingSlugs(): Promise<string[]> {
|
|
58
|
-
const res = await schedule.meetingTypes();
|
|
59
|
-
if (!res.ok) return [];
|
|
60
|
-
const { slugs } = res.body;
|
|
61
|
-
if (slugs) return slugs;
|
|
62
|
-
return TOPOLOGY === "spoke" ? [] : derivedFallbackSlugs();
|
|
63
|
-
}
|