create-ailk 0.5.0 → 0.6.1
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/README.md +55 -8
- package/component-catalog.json +336 -27
- package/component-catalog.md +25 -12
- package/dist/cli.js +54 -5
- package/dist/copy.d.ts +14 -0
- package/dist/copy.js +38 -1
- package/dist/lib/api-boot-assertion.d.ts +46 -0
- package/dist/lib/api-boot-assertion.js +70 -0
- package/dist/lib/extract-module-bundle.d.ts +20 -1
- package/dist/lib/extract-module-bundle.js +64 -5
- package/dist/lib/import-graph.d.ts +29 -0
- package/dist/lib/import-graph.js +140 -0
- package/dist/lib/module-license-gate.d.ts +6 -1
- package/dist/lib/module-license-gate.js +6 -1
- package/dist/lib/pro-callout.js +1 -1
- package/dist/module-architecture.d.ts +1 -0
- package/dist/module-architecture.js +253 -127
- package/dist/modules/commerce/0.6.0.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
- package/dist/modules/workspace/0.6.0.tar.gz +0 -0
- package/dist/programmatic.js +14 -10
- package/dist/surfaces.js +105 -7
- package/dist/template-manifest.d.ts +33 -0
- package/dist/template-manifest.js +99 -16
- package/dist/template-names.d.ts +1 -1
- package/dist/template-names.js +1 -0
- package/package.json +3 -2
- package/site-templates/marketing-site/DESIGN.md +148 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
- package/site-templates/marketing-site/ailk-template.json +1 -1
- package/site-templates/marketing-site-free/DESIGN.md +148 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
- package/site-templates/minimalism-site/DESIGN.md +148 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
- package/site-templates/minimalism-site/ailk-template.json +2 -1
- package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
- package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
- package/site-templates/saas-product/DESIGN.md +148 -0
- package/site-templates/simple-service/DESIGN.md +148 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
- package/site-templates/simple-service/ailk-template.json +10 -0
- package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
- package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
- package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/simple-service/provenance.json +4 -0
- package/templates/.env.example +72 -7
- package/templates/CLAUDE.md +2 -3
- package/templates/DESIGN.md +155 -0
- package/templates/README.md +42 -8
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
- package/templates/apps/api/.env.example +13 -0
- package/templates/apps/api/CLAUDE.md +26 -2
- package/templates/apps/api/jest.config.cjs +3 -0
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
- package/templates/apps/api/src/__tests__/server.test.ts +471 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
- package/templates/apps/api/src/config/index.ts +62 -0
- package/templates/apps/api/src/config/modules.ts +7 -3
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
- package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
- package/templates/apps/api/src/lib/content-adapter.ts +57 -31
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
- package/templates/apps/api/src/lib/error-serializer.ts +23 -3
- package/templates/apps/api/src/lib/outbound-http.ts +7 -2
- package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
- package/templates/apps/api/src/lib/site.ts +208 -9
- package/templates/apps/api/src/lib/stripe.ts +66 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
- package/templates/apps/api/src/openapi/spec.ts +543 -2
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
- package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
- package/templates/apps/api/src/routes/auth/user.ts +61 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
- package/templates/apps/api/src/routes/checkout/index.ts +34 -0
- package/templates/apps/api/src/routes/checkout/session.ts +139 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
- package/templates/apps/api/src/routes/content/get-page.ts +94 -12
- package/templates/apps/api/src/routes/content/index.ts +13 -73
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
- package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
- package/templates/apps/api/src/routes/content/search.ts +129 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
- package/templates/apps/api/src/routes/leads/index.ts +100 -13
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
- package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
- package/templates/apps/api/src/routes/requests/index.ts +54 -0
- package/templates/apps/api/src/routes/webhooks/README.md +265 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
- package/templates/apps/api/src/server.ts +439 -49
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
- package/templates/apps/api/src/services/abandon-email.ts +8 -2
- package/templates/apps/api/src/services/aeo-score.ts +49 -12
- package/templates/apps/api/src/services/audit-log.ts +668 -0
- package/templates/apps/api/src/services/consent.ts +18 -0
- package/templates/apps/api/src/services/default-tenant.ts +70 -31
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
- package/templates/apps/api/src/services/dunning-policy.ts +294 -0
- package/templates/apps/api/src/services/entitlements.ts +80 -0
- package/templates/apps/api/src/services/lead-promotion.ts +13 -14
- package/templates/apps/api/src/services/lead-routing.ts +161 -2
- package/templates/apps/api/src/services/leads-inbox.ts +23 -2
- package/templates/apps/api/src/services/listing-config.ts +27 -9
- package/templates/apps/api/src/services/project-listings.ts +201 -6
- package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
- package/templates/apps/api/src/services/request-confirm.ts +646 -0
- package/templates/apps/api/src/services/revalidation.ts +173 -0
- package/templates/apps/api/src/services/verified-email.ts +71 -0
- package/templates/apps/api/src/vercel-handler.ts +75 -35
- package/templates/apps/mcp/.env.example +17 -0
- package/templates/apps/mcp/CLAUDE.md +57 -24
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
- package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
- package/templates/apps/mcp/__tests__/index.test.ts +39 -0
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
- package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
- package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
- package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
- package/templates/apps/mcp/__tests__/server.test.ts +67 -14
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
- package/templates/apps/mcp/jest.config.cjs +13 -0
- package/templates/apps/mcp/package.json +2 -0
- package/templates/apps/mcp/src/abuse/index.ts +159 -24
- package/templates/apps/mcp/src/config/modules.ts +10 -5
- package/templates/apps/mcp/src/http-transport.ts +102 -29
- package/templates/apps/mcp/src/index.ts +73 -11
- package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
- package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
- package/templates/apps/mcp/src/lib/prisma.ts +39 -0
- package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
- package/templates/apps/mcp/src/server.ts +59 -35
- package/templates/apps/mcp/src/tools/audit.ts +26 -2
- package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
- package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
- package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
- package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
- package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
- package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
- package/templates/apps/mcp/src/tools/index.ts +6 -4
- package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
- package/templates/apps/mcp/src/tools/purchase.ts +128 -45
- package/templates/apps/mcp/src/tools/read_content.ts +20 -6
- package/templates/apps/mcp/src/tools/search_content.ts +11 -4
- package/templates/apps/web/.env.example +71 -5
- package/templates/apps/web/CLAUDE.md +44 -0
- package/templates/apps/web/PRODUCT.md +86 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
- package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/layout.tsx +133 -87
- package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
- package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
- package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
- package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
- package/templates/apps/web/app/api/revalidate/route.ts +194 -0
- package/templates/apps/web/auth.ts +407 -8
- package/templates/apps/web/jest.config.cjs +8 -4
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
- package/templates/apps/web/lib/auth-email.ts +318 -0
- package/templates/apps/web/lib/auth-env.ts +85 -0
- package/templates/apps/web/lib/auth-routes.ts +55 -0
- package/templates/apps/web/lib/meeting-slugs.ts +53 -0
- package/templates/apps/web/lib/site-brand.tsx +4 -8
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +187 -0
- package/templates/apps/web/middleware.ts +1 -1
- package/templates/apps/web/package.json +2 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
- package/templates/apps/web/src/lib/commerce-sections.ts +93 -0
- package/templates/content/_site.mdx +18 -3
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
- package/templates/database/CHANGELOG.md +149 -0
- package/templates/database/README.md +10 -3
- package/templates/database/__tests__/appointment.db.test.ts +106 -0
- package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
- package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
- package/templates/database/__tests__/tenancy.test.ts +141 -0
- package/templates/database/config/schema.prisma +79 -2
- package/templates/database/content/schema.prisma +87 -11
- package/templates/database/inbox/schema.prisma +452 -9
- package/templates/database/jest.config.cjs +5 -3
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
- package/templates/database/package.json +3 -2
- package/templates/database/tenancy.ts +86 -1
- package/templates/database/tsconfig.json +1 -0
- package/versions.json +34 -34
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
- package/templates/apps/api/src/routes/content/create.ts +0 -115
- package/templates/apps/api/src/routes/content/delete.ts +0 -82
- package/templates/apps/api/src/routes/content/update.ts +0 -112
- package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
- package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
- package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
- package/templates/apps/mcp/src/tools/create_page.ts +0 -96
- package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
- package/templates/apps/mcp/src/tools/update_page.ts +0 -92
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
- package/templates/apps/web/public/logo-dark.svg +0 -21
- package/templates/apps/web/public/logo-light.svg +0 -21
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
|
@@ -1,53 +1,99 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the ailkPurchase MCP tool
|
|
2
|
+
* Tests for the ailkPurchase MCP tool.
|
|
3
|
+
*
|
|
4
|
+
* Rewritten for #6001: three security reviews on PR #5991 flagged that an
|
|
5
|
+
* anonymous MCP caller could forward its own successUrl/cancelUrl/
|
|
6
|
+
* allowPromotionCodes to `checkout.createSession` — a phishing-amplification
|
|
7
|
+
* open redirect off the seller's real checkout — so the tool is now gated by
|
|
8
|
+
* `purchaseMcpInputSchema`, a `.strict()` `.pick()` of the shared
|
|
9
|
+
* `purchaseInputSchema` exposing exactly `productCode` + `quantity`. A call
|
|
10
|
+
* carrying any of the three excluded fields now FAILS validation rather than
|
|
11
|
+
* having them silently stripped, and both redirects are ALWAYS derived from
|
|
12
|
+
* `AILK_BASE_URL`.
|
|
13
|
+
*
|
|
14
|
+
* `purchaseInputSchema` itself is unchanged (#5971 shape, still shared with
|
|
15
|
+
* the Server Action / API route, which are not anonymous surfaces) — its own
|
|
16
|
+
* schema tests below stay as coverage of the SHARED schema, distinct from
|
|
17
|
+
* `purchaseMcpInputSchema`'s tests, which cover the tool's OWN gate.
|
|
18
|
+
*
|
|
19
|
+
* `__tests__/start_purchase.test.ts` owns the parity proof and the
|
|
20
|
+
* hosted-URL/unknown-offering behaviour; this suite keeps the schema coverage
|
|
21
|
+
* and the architectural guard alongside it.
|
|
3
22
|
*
|
|
4
23
|
* Acceptance criteria verified here:
|
|
5
|
-
* - purchaseInputSchema
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
10
|
-
*
|
|
24
|
+
* - purchaseInputSchema (the shared schema) accepts/rejects per the route's
|
|
25
|
+
* own validation — unchanged coverage, since the schema itself is
|
|
26
|
+
* byte-unchanged (#6001 AC3)
|
|
27
|
+
* - purchaseMcpInputSchema (the tool's own gate) accepts exactly
|
|
28
|
+
* productCode + quantity and REJECTS successUrl/cancelUrl/
|
|
29
|
+
* allowPromotionCodes outright (#6001 AC1)
|
|
30
|
+
* - purchaseOutputSchema validates the { url: string } live shape
|
|
31
|
+
* - ailkPurchase calls POST /v1/checkout/sessions through the api-client seam
|
|
32
|
+
* - ailkPurchase always derives successUrl/cancelUrl from AILK_BASE_URL, with
|
|
33
|
+
* and without the env var set, and never forwards allowPromotionCodes
|
|
34
|
+
* (#6001 AC2)
|
|
35
|
+
* - ailkPurchase returns { url } on success, a typed body on bad input
|
|
11
36
|
* - No Stripe SDK import in apps/mcp (architectural guard)
|
|
12
|
-
* - Parity: response shape matches purchaseAction Server Action shape
|
|
13
37
|
*/
|
|
14
38
|
|
|
15
|
-
import {
|
|
39
|
+
import { checkout } from "@working-theory/api-client";
|
|
16
40
|
import {
|
|
17
41
|
purchaseInputSchema as PurchaseInputSchema,
|
|
42
|
+
purchaseMcpInputSchema as PurchaseMcpInputSchema,
|
|
18
43
|
purchaseOutputSchema as PurchaseOutputSchema,
|
|
19
44
|
} from "@working-theory/validation";
|
|
20
45
|
|
|
21
|
-
|
|
46
|
+
import {
|
|
47
|
+
__setMcpCommerceClaimForTest,
|
|
48
|
+
resetMcpCommerceClaim,
|
|
49
|
+
} from "../../src/lib/commerce-claim.js";
|
|
50
|
+
import { ailkPurchase } from "../../src/tools/purchase.js";
|
|
51
|
+
|
|
52
|
+
jest.mock("@working-theory/api-client", () => ({
|
|
53
|
+
checkout: { createSession: jest.fn() },
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const createSession = checkout.createSession as jest.MockedFunction<
|
|
57
|
+
typeof checkout.createSession
|
|
58
|
+
>;
|
|
22
59
|
|
|
23
|
-
|
|
24
|
-
|
|
60
|
+
function sessionCreated(url: string) {
|
|
61
|
+
return { ok: true as const, body: { sessionId: "cs_test_id", url } };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
// Every direct `ailkPurchase` call below bypasses registration, so gate 2
|
|
66
|
+
// would answer the module-disabled body under the fail-closed default.
|
|
67
|
+
__setMcpCommerceClaimForTest("claimed");
|
|
68
|
+
});
|
|
25
69
|
|
|
26
70
|
afterEach(() => {
|
|
27
71
|
jest.clearAllMocks();
|
|
72
|
+
resetMcpCommerceClaim();
|
|
73
|
+
delete process.env.AILK_BASE_URL;
|
|
28
74
|
});
|
|
29
75
|
|
|
30
|
-
// ──
|
|
76
|
+
// ── Shared input schema (purchaseInputSchema — byte-unchanged, #6001 AC3) ──────
|
|
31
77
|
|
|
32
|
-
describe("PurchaseInputSchema", () => {
|
|
33
|
-
it("accepts valid input with
|
|
78
|
+
describe("PurchaseInputSchema — the shared schema (Server Action / API route)", () => {
|
|
79
|
+
it("accepts valid input with productCode and quantity", () => {
|
|
34
80
|
const result = PurchaseInputSchema.safeParse({
|
|
35
|
-
|
|
81
|
+
productCode: "price_test_123",
|
|
36
82
|
quantity: 1,
|
|
37
83
|
});
|
|
38
84
|
expect(result.success).toBe(true);
|
|
39
85
|
});
|
|
40
86
|
|
|
41
|
-
it("accepts valid input with
|
|
87
|
+
it("accepts valid input with productCode only (quantity defaults)", () => {
|
|
42
88
|
const result = PurchaseInputSchema.safeParse({
|
|
43
|
-
|
|
89
|
+
productCode: "price_test_abc",
|
|
44
90
|
});
|
|
45
91
|
expect(result.success).toBe(true);
|
|
46
92
|
});
|
|
47
93
|
|
|
48
94
|
it("accepts valid input with successUrl and cancelUrl", () => {
|
|
49
95
|
const result = PurchaseInputSchema.safeParse({
|
|
50
|
-
|
|
96
|
+
productCode: "price_test_123",
|
|
51
97
|
successUrl: "https://example.com/success",
|
|
52
98
|
cancelUrl: "https://example.com/cancel",
|
|
53
99
|
});
|
|
@@ -56,7 +102,7 @@ describe("PurchaseInputSchema", () => {
|
|
|
56
102
|
|
|
57
103
|
it("accepts an optional allowPromotionCodes boolean", () => {
|
|
58
104
|
const result = PurchaseInputSchema.safeParse({
|
|
59
|
-
|
|
105
|
+
productCode: "price_test_123",
|
|
60
106
|
allowPromotionCodes: true,
|
|
61
107
|
});
|
|
62
108
|
expect(result.success).toBe(true);
|
|
@@ -64,25 +110,39 @@ describe("PurchaseInputSchema", () => {
|
|
|
64
110
|
|
|
65
111
|
it("rejects a non-boolean allowPromotionCodes", () => {
|
|
66
112
|
const result = PurchaseInputSchema.safeParse({
|
|
67
|
-
|
|
113
|
+
productCode: "price_test_123",
|
|
68
114
|
allowPromotionCodes: "yes",
|
|
69
115
|
});
|
|
70
116
|
expect(result.success).toBe(false);
|
|
71
117
|
});
|
|
72
118
|
|
|
73
|
-
it("rejects input missing
|
|
119
|
+
it("rejects input missing productCode", () => {
|
|
74
120
|
const result = PurchaseInputSchema.safeParse({ quantity: 1 });
|
|
75
121
|
expect(result.success).toBe(false);
|
|
76
122
|
});
|
|
77
123
|
|
|
78
|
-
it("rejects input with empty
|
|
79
|
-
const result = PurchaseInputSchema.safeParse({
|
|
124
|
+
it("rejects input with empty productCode", () => {
|
|
125
|
+
const result = PurchaseInputSchema.safeParse({ productCode: "" });
|
|
126
|
+
expect(result.success).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("rejects a productCode outside the route's character set", () => {
|
|
130
|
+
const result = PurchaseInputSchema.safeParse({
|
|
131
|
+
productCode: "has spaces",
|
|
132
|
+
});
|
|
133
|
+
expect(result.success).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("rejects a productCode longer than 200 characters", () => {
|
|
137
|
+
const result = PurchaseInputSchema.safeParse({
|
|
138
|
+
productCode: "a".repeat(201),
|
|
139
|
+
});
|
|
80
140
|
expect(result.success).toBe(false);
|
|
81
141
|
});
|
|
82
142
|
|
|
83
143
|
it("rejects input with non-integer quantity", () => {
|
|
84
144
|
const result = PurchaseInputSchema.safeParse({
|
|
85
|
-
|
|
145
|
+
productCode: "price_x",
|
|
86
146
|
quantity: 1.5,
|
|
87
147
|
});
|
|
88
148
|
expect(result.success).toBe(false);
|
|
@@ -90,11 +150,98 @@ describe("PurchaseInputSchema", () => {
|
|
|
90
150
|
|
|
91
151
|
it("rejects input with quantity less than 1", () => {
|
|
92
152
|
const result = PurchaseInputSchema.safeParse({
|
|
93
|
-
|
|
153
|
+
productCode: "price_x",
|
|
94
154
|
quantity: 0,
|
|
95
155
|
});
|
|
96
156
|
expect(result.success).toBe(false);
|
|
97
157
|
});
|
|
158
|
+
|
|
159
|
+
it("rejects input with quantity above the route's maximum of 999", () => {
|
|
160
|
+
const result = PurchaseInputSchema.safeParse({
|
|
161
|
+
productCode: "price_x",
|
|
162
|
+
quantity: 1000,
|
|
163
|
+
});
|
|
164
|
+
expect(result.success).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("rejects a non-http(s) redirect URL the route would reject", () => {
|
|
168
|
+
const result = PurchaseInputSchema.safeParse({
|
|
169
|
+
productCode: "price_x",
|
|
170
|
+
successUrl: "javascript:alert(1)",
|
|
171
|
+
});
|
|
172
|
+
expect(result.success).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ── MCP-only input schema (purchaseMcpInputSchema — the tool's own gate) ───────
|
|
177
|
+
|
|
178
|
+
describe("PurchaseMcpInputSchema — the registered tool's OWN gate (#6001)", () => {
|
|
179
|
+
it("accepts productCode and quantity", () => {
|
|
180
|
+
const result = PurchaseMcpInputSchema.safeParse({
|
|
181
|
+
productCode: "price_test_123",
|
|
182
|
+
quantity: 2,
|
|
183
|
+
});
|
|
184
|
+
expect(result.success).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("accepts productCode alone (quantity optional)", () => {
|
|
188
|
+
const result = PurchaseMcpInputSchema.safeParse({
|
|
189
|
+
productCode: "price_test_123",
|
|
190
|
+
});
|
|
191
|
+
expect(result.success).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("rejects a call carrying successUrl", () => {
|
|
195
|
+
const result = PurchaseMcpInputSchema.safeParse({
|
|
196
|
+
productCode: "price_test_123",
|
|
197
|
+
successUrl: "https://example.com/success",
|
|
198
|
+
});
|
|
199
|
+
expect(result.success).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("rejects a call carrying cancelUrl", () => {
|
|
203
|
+
const result = PurchaseMcpInputSchema.safeParse({
|
|
204
|
+
productCode: "price_test_123",
|
|
205
|
+
cancelUrl: "https://example.com/cancel",
|
|
206
|
+
});
|
|
207
|
+
expect(result.success).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("rejects a call carrying allowPromotionCodes", () => {
|
|
211
|
+
const result = PurchaseMcpInputSchema.safeParse({
|
|
212
|
+
productCode: "price_test_123",
|
|
213
|
+
allowPromotionCodes: true,
|
|
214
|
+
});
|
|
215
|
+
expect(result.success).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("rejects a call carrying all three excluded fields at once", () => {
|
|
219
|
+
const result = PurchaseMcpInputSchema.safeParse({
|
|
220
|
+
productCode: "price_test_123",
|
|
221
|
+
successUrl: "https://attacker.example/phish",
|
|
222
|
+
cancelUrl: "https://attacker.example/cancel",
|
|
223
|
+
allowPromotionCodes: true,
|
|
224
|
+
});
|
|
225
|
+
expect(result.success).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("still rejects missing/invalid productCode, same as the shared schema", () => {
|
|
229
|
+
expect(PurchaseMcpInputSchema.safeParse({}).success).toBe(false);
|
|
230
|
+
expect(
|
|
231
|
+
PurchaseMcpInputSchema.safeParse({ productCode: "" }).success,
|
|
232
|
+
).toBe(false);
|
|
233
|
+
expect(
|
|
234
|
+
PurchaseMcpInputSchema.safeParse({ productCode: "has spaces" }).success,
|
|
235
|
+
).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("still rejects a quantity above the route's maximum of 999", () => {
|
|
239
|
+
const result = PurchaseMcpInputSchema.safeParse({
|
|
240
|
+
productCode: "price_x",
|
|
241
|
+
quantity: 1000,
|
|
242
|
+
});
|
|
243
|
+
expect(result.success).toBe(false);
|
|
244
|
+
});
|
|
98
245
|
});
|
|
99
246
|
|
|
100
247
|
// ── Output schema (live shape) ────────────────────────────────────────────────
|
|
@@ -121,79 +268,41 @@ describe("PurchaseOutputSchema", () => {
|
|
|
121
268
|
// ── ailkPurchase handler ──────────────────────────────────────────────────────
|
|
122
269
|
|
|
123
270
|
describe("ailkPurchase — valid input", () => {
|
|
124
|
-
it("calls POST /v1/checkout/sessions with mapped payload", async () => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
json: () =>
|
|
129
|
-
Promise.resolve({
|
|
130
|
-
sessionId: "cs_test_abc123",
|
|
131
|
-
url: "https://checkout.stripe.com/pay/cs_test_abc123",
|
|
132
|
-
}),
|
|
133
|
-
});
|
|
271
|
+
it("calls POST /v1/checkout/sessions with the mapped payload", async () => {
|
|
272
|
+
createSession.mockResolvedValueOnce(
|
|
273
|
+
sessionCreated("https://checkout.stripe.com/pay/cs_test_abc123"),
|
|
274
|
+
);
|
|
134
275
|
|
|
135
276
|
await ailkPurchase({
|
|
136
|
-
|
|
277
|
+
productCode: "price_test_123",
|
|
137
278
|
quantity: 1,
|
|
138
|
-
successUrl: "https://example.com/success",
|
|
139
|
-
cancelUrl: "https://example.com/cancel",
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
143
|
-
const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
144
|
-
expect(url).toMatch(/\/v1\/checkout\/sessions$/);
|
|
145
|
-
expect(opts.method).toBe("POST");
|
|
146
|
-
const body = JSON.parse(opts.body as string) as Record<string, unknown>;
|
|
147
|
-
expect(body.productCode).toBe("price_test_123");
|
|
148
|
-
expect(body.quantity).toBe(1);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("forwards allowPromotionCodes when supplied, omits it otherwise", async () => {
|
|
152
|
-
const okResponse = () => ({
|
|
153
|
-
ok: true,
|
|
154
|
-
status: 201,
|
|
155
|
-
json: () =>
|
|
156
|
-
Promise.resolve({
|
|
157
|
-
sessionId: "cs_test_promo",
|
|
158
|
-
url: "https://checkout.stripe.com/pay/cs_test_promo",
|
|
159
|
-
}),
|
|
160
279
|
});
|
|
161
|
-
mockFetch.mockResolvedValueOnce(okResponse());
|
|
162
280
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
281
|
+
expect(createSession).toHaveBeenCalledTimes(1);
|
|
282
|
+
const payload = createSession.mock.calls[0]![0];
|
|
283
|
+
expect(payload.productCode).toBe("price_test_123");
|
|
284
|
+
expect(payload.quantity).toBe(1);
|
|
285
|
+
});
|
|
167
286
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
287
|
+
it("never forwards allowPromotionCodes — it is not part of the tool's input", async () => {
|
|
288
|
+
createSession.mockResolvedValueOnce(
|
|
289
|
+
sessionCreated("https://checkout.stripe.com/pay/cs_test_promo"),
|
|
290
|
+
);
|
|
172
291
|
|
|
173
|
-
|
|
174
|
-
await ailkPurchase({ priceId: "price_test_123" });
|
|
292
|
+
await ailkPurchase({ productCode: "price_test_123" });
|
|
175
293
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
)
|
|
179
|
-
expect(withoutFlag).not.toHaveProperty("allowPromotionCodes");
|
|
294
|
+
expect(createSession.mock.calls[0]![0]).not.toHaveProperty(
|
|
295
|
+
"allowPromotionCodes",
|
|
296
|
+
);
|
|
180
297
|
});
|
|
181
298
|
|
|
182
299
|
it("returns { url } on success", async () => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
json: () =>
|
|
187
|
-
Promise.resolve({
|
|
188
|
-
sessionId: "cs_test_xyz",
|
|
189
|
-
url: "https://checkout.stripe.com/pay/cs_test_xyz",
|
|
190
|
-
}),
|
|
191
|
-
});
|
|
300
|
+
createSession.mockResolvedValueOnce(
|
|
301
|
+
sessionCreated("https://checkout.stripe.com/pay/cs_test_xyz"),
|
|
302
|
+
);
|
|
192
303
|
|
|
193
304
|
const output = await ailkPurchase({
|
|
194
|
-
|
|
195
|
-
successUrl: "https://example.com/success",
|
|
196
|
-
cancelUrl: "https://example.com/cancel",
|
|
305
|
+
productCode: "price_test_xyz",
|
|
197
306
|
});
|
|
198
307
|
|
|
199
308
|
expect(output).toEqual({
|
|
@@ -202,57 +311,125 @@ describe("ailkPurchase — valid input", () => {
|
|
|
202
311
|
});
|
|
203
312
|
|
|
204
313
|
it("output validates against PurchaseOutputSchema", async () => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
json: () =>
|
|
209
|
-
Promise.resolve({
|
|
210
|
-
sessionId: "cs_test_valid",
|
|
211
|
-
url: "https://checkout.stripe.com/pay/cs_test_valid",
|
|
212
|
-
}),
|
|
213
|
-
});
|
|
314
|
+
createSession.mockResolvedValueOnce(
|
|
315
|
+
sessionCreated("https://checkout.stripe.com/pay/cs_test_valid"),
|
|
316
|
+
);
|
|
214
317
|
|
|
215
|
-
const output = await ailkPurchase({
|
|
216
|
-
|
|
217
|
-
expect(result.success).toBe(true);
|
|
318
|
+
const output = await ailkPurchase({ productCode: "price_test_valid" });
|
|
319
|
+
expect(PurchaseOutputSchema.safeParse(output).success).toBe(true);
|
|
218
320
|
});
|
|
219
321
|
});
|
|
220
322
|
|
|
221
|
-
describe("ailkPurchase — invalid input (
|
|
222
|
-
it("
|
|
223
|
-
await
|
|
224
|
-
|
|
225
|
-
).
|
|
226
|
-
expect(
|
|
323
|
+
describe("ailkPurchase — invalid input (typed error body, no side effect)", () => {
|
|
324
|
+
it("returns validation_failed for a missing productCode before any call", async () => {
|
|
325
|
+
const result = await ailkPurchase({ quantity: 1 } as never);
|
|
326
|
+
|
|
327
|
+
expect(result).toMatchObject({ error: "validation_failed" });
|
|
328
|
+
expect(createSession).not.toHaveBeenCalled();
|
|
227
329
|
});
|
|
228
330
|
|
|
229
|
-
it("
|
|
230
|
-
await
|
|
231
|
-
|
|
232
|
-
).
|
|
233
|
-
expect(
|
|
331
|
+
it("returns validation_failed for an empty productCode before any call", async () => {
|
|
332
|
+
const result = await ailkPurchase({ productCode: "" });
|
|
333
|
+
|
|
334
|
+
expect(result).toMatchObject({ error: "validation_failed" });
|
|
335
|
+
expect(createSession).not.toHaveBeenCalled();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("returns validation_failed for a call carrying successUrl (#6001 AC1)", async () => {
|
|
339
|
+
const result = await ailkPurchase({
|
|
340
|
+
productCode: "price_test_123",
|
|
341
|
+
successUrl: "https://attacker.example/phish",
|
|
342
|
+
} as never);
|
|
343
|
+
|
|
344
|
+
expect(result).toMatchObject({ error: "validation_failed" });
|
|
345
|
+
expect(createSession).not.toHaveBeenCalled();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("returns validation_failed for a call carrying cancelUrl (#6001 AC1)", async () => {
|
|
349
|
+
const result = await ailkPurchase({
|
|
350
|
+
productCode: "price_test_123",
|
|
351
|
+
cancelUrl: "https://attacker.example/cancel",
|
|
352
|
+
} as never);
|
|
353
|
+
|
|
354
|
+
expect(result).toMatchObject({ error: "validation_failed" });
|
|
355
|
+
expect(createSession).not.toHaveBeenCalled();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("returns validation_failed for a call carrying allowPromotionCodes (#6001 AC1)", async () => {
|
|
359
|
+
const result = await ailkPurchase({
|
|
360
|
+
productCode: "price_test_123",
|
|
361
|
+
allowPromotionCodes: true,
|
|
362
|
+
} as never);
|
|
363
|
+
|
|
364
|
+
expect(result).toMatchObject({ error: "validation_failed" });
|
|
365
|
+
expect(createSession).not.toHaveBeenCalled();
|
|
234
366
|
});
|
|
235
367
|
});
|
|
236
368
|
|
|
237
|
-
describe("ailkPurchase —
|
|
238
|
-
it("
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
369
|
+
describe("ailkPurchase — always derives redirects from AILK_BASE_URL (#6001 AC2)", () => {
|
|
370
|
+
it("derives both redirect URLs from AILK_BASE_URL when it is set", async () => {
|
|
371
|
+
process.env.AILK_BASE_URL = "https://example.com";
|
|
372
|
+
createSession.mockResolvedValueOnce(
|
|
373
|
+
sessionCreated("https://checkout.stripe.com/pay/cs_test_base"),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
await ailkPurchase({ productCode: "price_test_123" });
|
|
377
|
+
|
|
378
|
+
expect(createSession).toHaveBeenCalledWith({
|
|
379
|
+
productCode: "price_test_123",
|
|
380
|
+
successUrl:
|
|
381
|
+
"https://example.com/purchase/success?session_id={CHECKOUT_SESSION_ID}",
|
|
382
|
+
cancelUrl: "https://example.com/purchase/cancel",
|
|
247
383
|
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("falls back to the tool's existing default when AILK_BASE_URL is unset", async () => {
|
|
387
|
+
delete process.env.AILK_BASE_URL;
|
|
388
|
+
delete process.env.NEXT_PUBLIC_BASE_URL;
|
|
389
|
+
createSession.mockResolvedValueOnce(
|
|
390
|
+
sessionCreated("https://checkout.stripe.com/pay/cs_test_fallback"),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
await ailkPurchase({ productCode: "price_test_123" });
|
|
394
|
+
|
|
395
|
+
expect(createSession).toHaveBeenCalledWith({
|
|
396
|
+
productCode: "price_test_123",
|
|
397
|
+
successUrl:
|
|
398
|
+
"http://localhost:3000/purchase/success?session_id={CHECKOUT_SESSION_ID}",
|
|
399
|
+
cancelUrl: "http://localhost:3000/purchase/cancel",
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// #6084 AC-6/D1 — the literal template token survives, and cancelUrl
|
|
404
|
+
// carries no session parameter at all.
|
|
405
|
+
it("successUrl carries the literal {CHECKOUT_SESSION_ID} token; cancelUrl carries no session parameter (#6084 D1)", async () => {
|
|
406
|
+
process.env.AILK_BASE_URL = "https://example.com";
|
|
407
|
+
createSession.mockResolvedValueOnce(
|
|
408
|
+
sessionCreated("https://checkout.stripe.com/pay/cs_test_braces"),
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
await ailkPurchase({ productCode: "price_test_123" });
|
|
412
|
+
|
|
413
|
+
const payload = createSession.mock.calls[0]![0] as {
|
|
414
|
+
successUrl: string;
|
|
415
|
+
cancelUrl: string;
|
|
416
|
+
};
|
|
417
|
+
expect(payload.successUrl).toContain("{CHECKOUT_SESSION_ID}");
|
|
418
|
+
expect(payload.cancelUrl).not.toContain("session_id");
|
|
419
|
+
expect(payload.cancelUrl).not.toContain("{CHECKOUT_SESSION_ID}");
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe("ailkPurchase — parity shape with purchaseAction", () => {
|
|
424
|
+
it("success shape is { url: string } matching the Server Action shape", async () => {
|
|
425
|
+
createSession.mockResolvedValueOnce(
|
|
426
|
+
sessionCreated("https://checkout.stripe.com/pay/cs_test_parity"),
|
|
427
|
+
);
|
|
248
428
|
|
|
249
429
|
const output = await ailkPurchase({
|
|
250
|
-
|
|
251
|
-
successUrl: "https://example.com/success",
|
|
252
|
-
cancelUrl: "https://example.com/cancel",
|
|
430
|
+
productCode: "price_parity",
|
|
253
431
|
});
|
|
254
432
|
|
|
255
|
-
// Shape must match purchaseAction's success shape
|
|
256
433
|
expect(output).toHaveProperty("url");
|
|
257
434
|
expect(typeof (output as { url: string }).url).toBe("string");
|
|
258
435
|
});
|
|
@@ -22,6 +22,17 @@ module.exports = {
|
|
|
22
22
|
'^@working-theory/aeo/(.*)$': '<rootDir>/../../packages/aeo/src/$1',
|
|
23
23
|
'^@working-theory/content-adapters$': '<rootDir>/../../packages/content-adapters/src/index.ts',
|
|
24
24
|
'^@working-theory/content-adapters/(.*)$': '<rootDir>/../../packages/content-adapters/src/$1',
|
|
25
|
+
// The constants + one-workspace predicate subpath only (#5876 spec D11) —
|
|
26
|
+
// no @prisma/client import, so ts-jest compiles it directly like every
|
|
27
|
+
// other workspace mapping below. The database/dist/ build is empty in a
|
|
28
|
+
// fresh checkout, and the published dist is ESM, which Jest's CJS preset
|
|
29
|
+
// can't load. The CLIENT root (`@working-theory/database`) stays
|
|
30
|
+
// deliberately UNMAPPED — see apps/mcp/src/lib/prisma.ts.
|
|
31
|
+
'^@working-theory/database/tenancy$': '<rootDir>/../../database/tenancy.ts',
|
|
32
|
+
// tenancy.ts's isSentinel import (above) is a VALUE import, so every
|
|
33
|
+
// suite that loads the tenancy subpath transitively loads this one too
|
|
34
|
+
// — mirrors apps/api/jest.config.cjs's identical mapping.
|
|
35
|
+
'^@working-theory/ids$': '<rootDir>/../../packages/ids/src/index.ts',
|
|
25
36
|
'^@working-theory/license$': '<rootDir>/../../packages/license/src/index.ts',
|
|
26
37
|
'^@working-theory/metadata$': '<rootDir>/../../packages/metadata/src/index.ts',
|
|
27
38
|
'^@working-theory/schema$': '<rootDir>/../../packages/schema/src/index.ts',
|
|
@@ -61,6 +72,8 @@ module.exports = {
|
|
|
61
72
|
'@working-theory/api-client': ['../../packages/api-client/src/index.ts'],
|
|
62
73
|
'@working-theory/content-adapters': ['../../packages/content-adapters/src/index.ts'],
|
|
63
74
|
'@working-theory/content-adapters/*': ['../../packages/content-adapters/src/*'],
|
|
75
|
+
'@working-theory/database/tenancy': ['../../database/tenancy.ts'],
|
|
76
|
+
'@working-theory/ids': ['../../packages/ids/src/index.ts'],
|
|
64
77
|
'@working-theory/license': ['../../packages/license/src/index.ts'],
|
|
65
78
|
'@working-theory/metadata': ['../../packages/metadata/src/index.ts'],
|
|
66
79
|
'@working-theory/schema': ['../../packages/schema/src/index.ts'],
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
"@fastify/cors": "^11.2.0",
|
|
19
19
|
"@fastify/rate-limit": "^10.3.0",
|
|
20
20
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
|
+
"@prisma/adapter-pg": "^7.8.0",
|
|
21
22
|
"@working-theory/aeo": "workspace:*",
|
|
22
23
|
"@working-theory/api-client": "workspace:*",
|
|
23
24
|
"@working-theory/content-adapters": "workspace:*",
|
|
25
|
+
"@working-theory/database": "workspace:*",
|
|
24
26
|
"@working-theory/license": "workspace:*",
|
|
25
27
|
"@working-theory/metadata": "workspace:*",
|
|
26
28
|
"@working-theory/schema": "workspace:*",
|