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
|
@@ -387,7 +387,7 @@ export async function sendCompletionEmail(
|
|
|
387
387
|
|
|
388
388
|
export async function fulfillDeliverable(
|
|
389
389
|
input: FulfillDeliverableInput,
|
|
390
|
-
): Promise<
|
|
390
|
+
): Promise<{ grantId: string } | undefined> {
|
|
391
391
|
const {
|
|
392
392
|
siteId,
|
|
393
393
|
flowSlug,
|
|
@@ -403,7 +403,7 @@ export async function fulfillDeliverable(
|
|
|
403
403
|
// the call on this being present; re-checking here is defense in depth,
|
|
404
404
|
// never load-bearing for the "no block → no call" AC4 regression floor.
|
|
405
405
|
const block = configSnapshot.deliverables;
|
|
406
|
-
if (!block) return;
|
|
406
|
+
if (!block) return undefined;
|
|
407
407
|
|
|
408
408
|
const logCtx = { siteId, flowSlug, sessionId };
|
|
409
409
|
|
|
@@ -412,7 +412,7 @@ export async function fulfillDeliverable(
|
|
|
412
412
|
const entry = resolveEntry(block.assets, segment);
|
|
413
413
|
if (!entry) {
|
|
414
414
|
logOutcome(logCtx, "no_entry");
|
|
415
|
-
return;
|
|
415
|
+
return undefined;
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
// FEAT-074 U4b (#4718) D4 — the `playbook:` virtual asset: a per-lead
|
|
@@ -439,7 +439,7 @@ export async function fulfillDeliverable(
|
|
|
439
439
|
});
|
|
440
440
|
if (!instance) {
|
|
441
441
|
logOutcome(logCtx, "playbook_missing");
|
|
442
|
-
return;
|
|
442
|
+
return undefined;
|
|
443
443
|
}
|
|
444
444
|
assetId = entry.assetKey;
|
|
445
445
|
} else {
|
|
@@ -464,6 +464,13 @@ export async function fulfillDeliverable(
|
|
|
464
464
|
assetId = resolvedAssetId;
|
|
465
465
|
grantWorkspaceId = workspaceId;
|
|
466
466
|
} else {
|
|
467
|
+
// #8848 D3 (SEC-8848-B) — a `deliverable` flow resolves against the
|
|
468
|
+
// Asset store only: it is new, so it has no legacy key to keep, and the
|
|
469
|
+
// legacy store also holds the founders' signup CSVs.
|
|
470
|
+
if (configSnapshot.deliverable !== undefined) {
|
|
471
|
+
logOutcome(logCtx, "asset_missing");
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
467
474
|
// #6310 D2/D3 — legacy fallback. A missing row (config references an
|
|
468
475
|
// un-uploaded key) → same skip-and-log posture as before.
|
|
469
476
|
// #6257 — the LEGACY store's read, scoped by the same resolved
|
|
@@ -476,7 +483,7 @@ export async function fulfillDeliverable(
|
|
|
476
483
|
|
|
477
484
|
if (!asset) {
|
|
478
485
|
logOutcome(logCtx, "asset_missing");
|
|
479
|
-
return;
|
|
486
|
+
return undefined;
|
|
480
487
|
}
|
|
481
488
|
assetId = asset.id;
|
|
482
489
|
}
|
|
@@ -527,6 +534,7 @@ export async function fulfillDeliverable(
|
|
|
527
534
|
defaultLocale: configSnapshot.defaultLocale,
|
|
528
535
|
copy: block.copy,
|
|
529
536
|
});
|
|
537
|
+
return { grantId: grant.id };
|
|
530
538
|
}
|
|
531
539
|
|
|
532
540
|
// ─── #3998 D3 — resend: reuses the existing grant id, never mints ───────────────
|
|
@@ -15,15 +15,24 @@
|
|
|
15
15
|
* foundation fills `features` + `limits.static` (tier + seat count) and returns
|
|
16
16
|
* `limits.metered` as the reserved empty shape FEAT-047 (#3679) fills — this
|
|
17
17
|
* spec builds NO metering engine (spec D5).
|
|
18
|
+
*
|
|
19
|
+
* #9067 D5 — the Billing tab's plan card also reads here: the licence expiry,
|
|
20
|
+
* the Organization's one open plan Checkout, and the customer's billing
|
|
21
|
+
* summary. That summary is the one Stripe read this resolver makes, through
|
|
22
|
+
* `lib/stripe.ts`.
|
|
18
23
|
*/
|
|
19
|
-
import type
|
|
24
|
+
import { checkLicense, type Entitlements } from "@working-theory/license";
|
|
25
|
+
import { logger } from "@working-theory/observability";
|
|
20
26
|
import {
|
|
21
27
|
MAGNET_KIND_MIN_RANK,
|
|
28
|
+
orgBillingDetailsSchema,
|
|
22
29
|
planRank,
|
|
23
30
|
type FlowConfig,
|
|
31
|
+
type OrgBillingDetails,
|
|
24
32
|
} from "@working-theory/validation";
|
|
25
33
|
|
|
26
34
|
import { prisma } from "../lib/prisma.js";
|
|
35
|
+
import { readCustomerBillingSummary } from "../lib/stripe.js";
|
|
27
36
|
|
|
28
37
|
/**
|
|
29
38
|
* Feature flags unlocked per tier. Free unlocks none; every paid tier unlocks
|
|
@@ -180,3 +189,150 @@ export async function isBillingCustomerAnOrganization(
|
|
|
180
189
|
});
|
|
181
190
|
return org !== null;
|
|
182
191
|
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* #9067 D5 — the deployment licence's expiry, as an ISO string, or `null`
|
|
195
|
+
* for a keyless, expired or invalid licence. The trial state's date
|
|
196
|
+
* (C2). Never throws: a failed read is `null`.
|
|
197
|
+
*/
|
|
198
|
+
export async function resolveLicenseExpiresAt(): Promise<string | null> {
|
|
199
|
+
try {
|
|
200
|
+
const license = await checkLicense();
|
|
201
|
+
return license.expiresAt?.toISOString() ?? null;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* #9067 D13 — the one plan Checkout this Organization has open, or `null`.
|
|
209
|
+
* Server-side only: the session id is never served. An unknown
|
|
210
|
+
* Organization reads `null`.
|
|
211
|
+
*/
|
|
212
|
+
export async function readOrgPlanCheckoutSession(
|
|
213
|
+
organizationId: string,
|
|
214
|
+
): Promise<string | null> {
|
|
215
|
+
const org = await prisma.organization.findUnique({
|
|
216
|
+
where: { id: organizationId },
|
|
217
|
+
select: { planCheckoutSessionId: true },
|
|
218
|
+
});
|
|
219
|
+
return org?.planCheckoutSessionId ?? null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* #9067 D3 step 9b — record the plan Checkout the route just opened, as one
|
|
224
|
+
* compare-and-set: only while the column still holds `expected` and no
|
|
225
|
+
* customer is bound. Answers whether one row moved; throws on a database
|
|
226
|
+
* error.
|
|
227
|
+
*/
|
|
228
|
+
export async function setOrgPlanCheckoutSession(
|
|
229
|
+
organizationId: string,
|
|
230
|
+
expected: string | null,
|
|
231
|
+
next: string,
|
|
232
|
+
): Promise<boolean> {
|
|
233
|
+
const { count } = await prisma.organization.updateMany({
|
|
234
|
+
where: { id: organizationId, planCheckoutSessionId: expected, billingCustomerId: null },
|
|
235
|
+
data: { planCheckoutSessionId: next },
|
|
236
|
+
});
|
|
237
|
+
return count === 1;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* #9067 7b (code review) — bind the Organization to the customer a COMPLETED
|
|
242
|
+
* plan Checkout created, when the webhook's bind never landed (a lost event,
|
|
243
|
+
* a database error the webhook swallowed). The same guarded compare-and-set
|
|
244
|
+
* the webhook runs: only while the column still holds `sessionId` and no
|
|
245
|
+
* customer is bound, and it clears the session. Answers whether one row
|
|
246
|
+
* moved; throws on a database error.
|
|
247
|
+
*/
|
|
248
|
+
export async function bindOrgPlanCheckout(
|
|
249
|
+
organizationId: string,
|
|
250
|
+
sessionId: string,
|
|
251
|
+
customerId: string,
|
|
252
|
+
): Promise<boolean> {
|
|
253
|
+
const { count } = await prisma.organization.updateMany({
|
|
254
|
+
where: { id: organizationId, planCheckoutSessionId: sessionId, billingCustomerId: null },
|
|
255
|
+
data: { billingProvider: "stripe", billingCustomerId: customerId, planCheckoutSessionId: null },
|
|
256
|
+
});
|
|
257
|
+
return count === 1;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** How long one customer's summary is reused, a failure included (#9067 NFR-1). */
|
|
261
|
+
export const ORG_BILLING_SUMMARY_TTL_MS = 60_000;
|
|
262
|
+
/** The most customers held at once; the oldest entry is evicted at the cap. */
|
|
263
|
+
export const ORG_BILLING_SUMMARY_CACHE_MAX = 2_000;
|
|
264
|
+
/** The longest one summary read may take before the keys are left absent (NFR-2). */
|
|
265
|
+
export const ORG_BILLING_SUMMARY_TIMEOUT_MS = 3_000;
|
|
266
|
+
|
|
267
|
+
type OrgBillingSummaryKeys = Partial<Pick<OrgBillingDetails, "renewsAt" | "sites" | "paymentMethod">>;
|
|
268
|
+
|
|
269
|
+
/** Per customer: the settled keys and when they expire. Insertion order is age. */
|
|
270
|
+
const orgBillingSummaryCache = new Map<string, { keys: OrgBillingSummaryKeys; expiresAt: number }>();
|
|
271
|
+
/** Per customer: the one read in flight, joined by every concurrent miss. */
|
|
272
|
+
const orgBillingSummaryInFlight = new Map<string, Promise<OrgBillingSummaryKeys>>();
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* #9067 D5 — the Organization customer's renewal date, site count and card,
|
|
276
|
+
* from the cache when fresh, else one read shared by every concurrent caller
|
|
277
|
+
* of the same customer. The member read's wrapper (`services/subscription.ts`),
|
|
278
|
+
* rebuilt here because importing it would be a cycle. A throw or a timeout
|
|
279
|
+
* answers `{}` (every key absent) and is cached like a success; a value
|
|
280
|
+
* outside `orgBillingDetailsSchema`'s bounds is `null`.
|
|
281
|
+
*/
|
|
282
|
+
export async function readOrgBillingSummary(customerId: string): Promise<OrgBillingSummaryKeys> {
|
|
283
|
+
const cached = orgBillingSummaryCache.get(customerId);
|
|
284
|
+
if (cached && cached.expiresAt > Date.now()) return cached.keys;
|
|
285
|
+
const pending = orgBillingSummaryInFlight.get(customerId);
|
|
286
|
+
if (pending) return pending;
|
|
287
|
+
|
|
288
|
+
const read = settleOrgBillingSummary(customerId).then((keys) => {
|
|
289
|
+
orgBillingSummaryCache.delete(customerId);
|
|
290
|
+
if (orgBillingSummaryCache.size >= ORG_BILLING_SUMMARY_CACHE_MAX) {
|
|
291
|
+
const oldest = orgBillingSummaryCache.keys().next().value;
|
|
292
|
+
if (oldest !== undefined) orgBillingSummaryCache.delete(oldest);
|
|
293
|
+
}
|
|
294
|
+
orgBillingSummaryCache.set(customerId, {
|
|
295
|
+
keys,
|
|
296
|
+
expiresAt: Date.now() + ORG_BILLING_SUMMARY_TTL_MS,
|
|
297
|
+
});
|
|
298
|
+
return keys;
|
|
299
|
+
});
|
|
300
|
+
// Cleared however the read ends, so a rejection is never served forever.
|
|
301
|
+
void read.finally(() => orgBillingSummaryInFlight.delete(customerId)).catch(() => undefined);
|
|
302
|
+
orgBillingSummaryInFlight.set(customerId, read);
|
|
303
|
+
return read;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function settleOrgBillingSummary(customerId: string): Promise<OrgBillingSummaryKeys> {
|
|
307
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
308
|
+
try {
|
|
309
|
+
const timedOut = new Promise<"timeout">((resolve) => {
|
|
310
|
+
timer = setTimeout(() => resolve("timeout"), ORG_BILLING_SUMMARY_TIMEOUT_MS);
|
|
311
|
+
});
|
|
312
|
+
const summary = await Promise.race([readCustomerBillingSummary(customerId), timedOut]);
|
|
313
|
+
if (summary === "timeout") throw new Error("timeout");
|
|
314
|
+
const { shape } = orgBillingDetailsSchema;
|
|
315
|
+
const renewsAt = shape.renewsAt.safeParse(summary.renewsAt);
|
|
316
|
+
const sites = shape.sites.safeParse(summary.sites);
|
|
317
|
+
const paymentMethod = shape.paymentMethod.safeParse(summary.paymentMethod);
|
|
318
|
+
return {
|
|
319
|
+
renewsAt: renewsAt.success ? (renewsAt.data ?? null) : null,
|
|
320
|
+
sites: sites.success ? (sites.data ?? null) : null,
|
|
321
|
+
paymentMethod: paymentMethod.success ? (paymentMethod.data ?? null) : null,
|
|
322
|
+
};
|
|
323
|
+
} catch {
|
|
324
|
+
logger.warn(
|
|
325
|
+
{ route: "billing", outcome: "summary_unavailable" },
|
|
326
|
+
"[billing] organization billing summary unavailable",
|
|
327
|
+
);
|
|
328
|
+
return {};
|
|
329
|
+
} finally {
|
|
330
|
+
clearTimeout(timer);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Test seam: forget every cached and in-flight Organization summary. */
|
|
335
|
+
export function __resetOrgBillingSummaryCache(): void {
|
|
336
|
+
orgBillingSummaryCache.clear();
|
|
337
|
+
orgBillingSummaryInFlight.clear();
|
|
338
|
+
}
|