create-ailk 0.1.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/LICENSE +201 -0
- package/README.md +39 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +80 -0
- package/dist/copy.d.ts +8 -0
- package/dist/copy.js +109 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/lib/pro-callout.d.ts +12 -0
- package/dist/lib/pro-callout.js +20 -0
- package/dist/parse-args.d.ts +13 -0
- package/dist/parse-args.js +81 -0
- package/dist/programmatic.d.ts +49 -0
- package/dist/programmatic.js +89 -0
- package/dist/substitute.d.ts +31 -0
- package/dist/substitute.js +45 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.js +70 -0
- package/package.json +47 -0
- package/templates/.claude/.gitkeep +0 -0
- package/templates/.claude/agents/web.md +80 -0
- package/templates/.claude/rules/architecture.md +62 -0
- package/templates/.claude/rules/preserve-existing-style.md +51 -0
- package/templates/.claude/skills/README.md +43 -0
- package/templates/.claude/skills/add-schema/SKILL.md +270 -0
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +200 -0
- package/templates/.claude/skills/add-schema/references/file-finding.md +123 -0
- package/templates/.claude/skills/add-schema/references/schema-types.md +131 -0
- package/templates/.claude/skills/audit-site/SKILL.md +213 -0
- package/templates/.claude/skills/audit-site/references/common-errors.md +113 -0
- package/templates/.claude/skills/audit-site/references/findings-glossary.md +138 -0
- package/templates/.claude/skills/audit-site/references/severity-levels.md +114 -0
- package/templates/.claude/skills/provision-config/SKILL.md +243 -0
- package/templates/.claude/skills/provision-config/references/env-vars-template.md +184 -0
- package/templates/.claude/skills/provision-config/references/runbook-template.md +248 -0
- package/templates/.claude/skills/provision-config/references/vercel-json-template.md +98 -0
- package/templates/.claude/skills/scaffold-auth/SKILL.md +261 -0
- package/templates/.claude/skills/scaffold-auth/references/env-vars.md +91 -0
- package/templates/.claude/skills/scaffold-auth/references/oauth-app-setup.md +171 -0
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +310 -0
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +71 -0
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +83 -0
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +72 -0
- package/templates/.claude/skills/scaffold-page/SKILL.md +243 -0
- package/templates/.claude/skills/scaffold-page/references/page-types.md +169 -0
- package/templates/.claude/skills/scaffold-page/templates/content.mdx.tmpl +28 -0
- package/templates/.claude/skills/scaffold-page/templates/route.tsx.tmpl +28 -0
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +242 -0
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +287 -0
- package/templates/.env.example +205 -0
- package/templates/.gitkeep +2 -0
- package/templates/CLAUDE.md +142 -0
- package/templates/CONVENTIONS.md +222 -0
- package/templates/README.md +110 -0
- package/templates/apps/api/.env.example +40 -0
- package/templates/apps/api/CLAUDE.md +63 -0
- package/templates/apps/api/api/index.ts +144 -0
- package/templates/apps/api/jest.config.cjs +74 -0
- package/templates/apps/api/package.json +54 -0
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +138 -0
- package/templates/apps/api/src/__tests__/factories/index.ts +70 -0
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +82 -0
- package/templates/apps/api/src/__tests__/index.test.ts +118 -0
- package/templates/apps/api/src/__tests__/server.test.ts +207 -0
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +126 -0
- package/templates/apps/api/src/__tests__/setup.ts +24 -0
- package/templates/apps/api/src/cli.ts +30 -0
- package/templates/apps/api/src/config/__tests__/index.test.ts +97 -0
- package/templates/apps/api/src/config/index.ts +56 -0
- package/templates/apps/api/src/index.ts +32 -0
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +15 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +45 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +178 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +100 -0
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +94 -0
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +144 -0
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +8 -0
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +227 -0
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +165 -0
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +129 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +74 -0
- package/templates/apps/api/src/lib/content-adapter.ts +86 -0
- package/templates/apps/api/src/lib/error-serializer.ts +77 -0
- package/templates/apps/api/src/lib/errors.ts +95 -0
- package/templates/apps/api/src/lib/file-fallback.ts +87 -0
- package/templates/apps/api/src/lib/lead-capture-store.ts +110 -0
- package/templates/apps/api/src/lib/locales.ts +201 -0
- package/templates/apps/api/src/lib/outbound-http.ts +122 -0
- package/templates/apps/api/src/lib/prisma.ts +21 -0
- package/templates/apps/api/src/lib/require-paid-tier.ts +53 -0
- package/templates/apps/api/src/lib/stripe.ts +30 -0
- package/templates/apps/api/src/lib/webhook-idempotency.ts +92 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +111 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +172 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +119 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +138 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +149 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +139 -0
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +175 -0
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +76 -0
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +153 -0
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +72 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +292 -0
- package/templates/apps/api/src/middleware/auth.ts +246 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +132 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +129 -0
- package/templates/apps/api/src/openapi/index.ts +12 -0
- package/templates/apps/api/src/openapi/spec.ts +408 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +193 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +201 -0
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +294 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +486 -0
- package/templates/apps/api/src/routes/auth/tokens.ts +199 -0
- package/templates/apps/api/src/routes/auth/user.ts +244 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +588 -0
- package/templates/apps/api/src/routes/checkout/index.ts +13 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +185 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +239 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +201 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +141 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +122 -0
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +44 -0
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +118 -0
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +51 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +252 -0
- package/templates/apps/api/src/routes/content/create.ts +114 -0
- package/templates/apps/api/src/routes/content/delete.ts +81 -0
- package/templates/apps/api/src/routes/content/get-page.ts +98 -0
- package/templates/apps/api/src/routes/content/index.ts +32 -0
- package/templates/apps/api/src/routes/content/list-pages.ts +83 -0
- package/templates/apps/api/src/routes/content/list-types.ts +19 -0
- package/templates/apps/api/src/routes/content/rate-limit.ts +13 -0
- package/templates/apps/api/src/routes/content/slug.ts +9 -0
- package/templates/apps/api/src/routes/content/update.ts +111 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +415 -0
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +165 -0
- package/templates/apps/api/src/routes/leads/index.ts +130 -0
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +296 -0
- package/templates/apps/api/src/routes/test/auth/session.ts +141 -0
- package/templates/apps/api/src/routes/webhooks/README.md +57 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +247 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +198 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +633 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +41 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +253 -0
- package/templates/apps/api/src/server.ts +157 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +395 -0
- package/templates/apps/api/src/services/enrichment-shim.ts +53 -0
- package/templates/apps/api/src/services/lead-routing.ts +338 -0
- package/templates/apps/api/tsconfig.json +34 -0
- package/templates/apps/api/vercel.json +17 -0
- package/templates/apps/mcp/.env.example +52 -0
- package/templates/apps/mcp/CLAUDE.md +84 -0
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +52 -0
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +46 -0
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +36 -0
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +54 -0
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +110 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +213 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +217 -0
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +52 -0
- package/templates/apps/mcp/__tests__/create_page.test.ts +201 -0
- package/templates/apps/mcp/__tests__/delete_page.test.ts +176 -0
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +82 -0
- package/templates/apps/mcp/__tests__/get_faq.test.ts +80 -0
- package/templates/apps/mcp/__tests__/get_locations.test.ts +65 -0
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +90 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +80 -0
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +81 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +323 -0
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +75 -0
- package/templates/apps/mcp/__tests__/lib/require-paid-tier.test.ts +75 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +112 -0
- package/templates/apps/mcp/__tests__/locale.test.ts +135 -0
- package/templates/apps/mcp/__tests__/parity.test.ts +205 -0
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +34 -0
- package/templates/apps/mcp/__tests__/read_content.test.ts +153 -0
- package/templates/apps/mcp/__tests__/request_callback.test.ts +163 -0
- package/templates/apps/mcp/__tests__/search_content.test.ts +153 -0
- package/templates/apps/mcp/__tests__/server.test.ts +115 -0
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +165 -0
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +161 -0
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +227 -0
- package/templates/apps/mcp/__tests__/tools/scaffold.test.ts +237 -0
- package/templates/apps/mcp/__tests__/update_page.test.ts +195 -0
- package/templates/apps/mcp/jest.config.cjs +82 -0
- package/templates/apps/mcp/package.json +37 -0
- package/templates/apps/mcp/src/abuse/daily-cap.ts +47 -0
- package/templates/apps/mcp/src/abuse/email-validator.ts +25 -0
- package/templates/apps/mcp/src/abuse/honeypot.ts +21 -0
- package/templates/apps/mcp/src/abuse/index.ts +111 -0
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +58 -0
- package/templates/apps/mcp/src/http-transport.ts +303 -0
- package/templates/apps/mcp/src/index.ts +67 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +194 -0
- package/templates/apps/mcp/src/lib/errors.ts +61 -0
- package/templates/apps/mcp/src/lib/locales.ts +171 -0
- package/templates/apps/mcp/src/lib/require-paid-tier.ts +44 -0
- package/templates/apps/mcp/src/lib/resolve-locale.ts +30 -0
- package/templates/apps/mcp/src/server.ts +139 -0
- package/templates/apps/mcp/src/tools/audit.ts +103 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +52 -0
- package/templates/apps/mcp/src/tools/create_page.ts +95 -0
- package/templates/apps/mcp/src/tools/delete_page.ts +73 -0
- package/templates/apps/mcp/src/tools/get_business_hours.ts +66 -0
- package/templates/apps/mcp/src/tools/get_faq.ts +69 -0
- package/templates/apps/mcp/src/tools/get_locations.ts +85 -0
- package/templates/apps/mcp/src/tools/get_offerings.ts +66 -0
- package/templates/apps/mcp/src/tools/get_pricing.ts +74 -0
- package/templates/apps/mcp/src/tools/get_site_info.ts +84 -0
- package/templates/apps/mcp/src/tools/index.ts +125 -0
- package/templates/apps/mcp/src/tools/list_pages.ts +72 -0
- package/templates/apps/mcp/src/tools/purchase.ts +89 -0
- package/templates/apps/mcp/src/tools/read_content.ts +61 -0
- package/templates/apps/mcp/src/tools/request_callback.ts +51 -0
- package/templates/apps/mcp/src/tools/scaffold.ts +119 -0
- package/templates/apps/mcp/src/tools/search_content.ts +59 -0
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +47 -0
- package/templates/apps/mcp/src/tools/update_page.ts +91 -0
- package/templates/apps/mcp/tsconfig.json +19 -0
- package/templates/apps/web/.env.example +47 -0
- package/templates/apps/web/CLAUDE.md +81 -0
- package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +94 -0
- package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +75 -0
- package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +82 -0
- package/templates/apps/web/__tests__/auth/env-helpers.test.ts +168 -0
- package/templates/apps/web/__tests__/authority-list-routes.test.tsx +208 -0
- package/templates/apps/web/__tests__/better-stack.test.ts +66 -0
- package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +87 -0
- package/templates/apps/web/__tests__/blog-source-populated.test.ts +130 -0
- package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +60 -0
- package/templates/apps/web/__tests__/google-analytics.test.tsx +104 -0
- package/templates/apps/web/__tests__/i18n/middleware.test.ts +119 -0
- package/templates/apps/web/__tests__/i18n/routing.test.ts +114 -0
- package/templates/apps/web/__tests__/i18n/translations.test.ts +191 -0
- package/templates/apps/web/__tests__/json-ld.test.ts +98 -0
- package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +580 -0
- package/templates/apps/web/__tests__/layout-stealth.test.ts +39 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +103 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +114 -0
- package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +94 -0
- package/templates/apps/web/__tests__/page.test.tsx +230 -0
- package/templates/apps/web/__tests__/products-services-phase1.test.tsx +321 -0
- package/templates/apps/web/__tests__/projects-routes.test.tsx +392 -0
- package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +475 -0
- package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +53 -0
- package/templates/apps/web/__tests__/routes/blog-route.test.tsx +191 -0
- package/templates/apps/web/__tests__/routes/llms-txt.test.ts +123 -0
- package/templates/apps/web/__tests__/routes/robots-txt.test.ts +52 -0
- package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +78 -0
- package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +203 -0
- package/templates/apps/web/__tests__/section-routes.test.tsx +693 -0
- package/templates/apps/web/__tests__/security-headers.test.ts +201 -0
- package/templates/apps/web/__tests__/sentry-config.test.ts +116 -0
- package/templates/apps/web/__tests__/setup.ts +82 -0
- package/templates/apps/web/__tests__/sitemap-locales.test.ts +415 -0
- package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +844 -0
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +81 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +110 -0
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +58 -0
- package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +120 -0
- package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +202 -0
- package/templates/apps/web/app/[locale]/about/page.tsx +31 -0
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +173 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +45 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/careers/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +113 -0
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/clients/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +130 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +152 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +92 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +79 -0
- package/templates/apps/web/app/[locale]/contact/error.tsx +24 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +38 -0
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +119 -0
- package/templates/apps/web/app/[locale]/courses/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +160 -0
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +25 -0
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +117 -0
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +58 -0
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +118 -0
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +72 -0
- package/templates/apps/web/app/[locale]/downloads/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +47 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +130 -0
- package/templates/apps/web/app/[locale]/events/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/faq/page.tsx +36 -0
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +78 -0
- package/templates/apps/web/app/[locale]/how-to/page.tsx +40 -0
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/integrations/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/launch-service/page.tsx +31 -0
- package/templates/apps/web/app/[locale]/layout.tsx +241 -0
- package/templates/apps/web/app/[locale]/page.tsx +41 -0
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +113 -0
- package/templates/apps/web/app/[locale]/partners/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/products/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +138 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +30 -0
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +51 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/resources/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +43 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +129 -0
- package/templates/apps/web/app/[locale]/services/page.tsx +25 -0
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +81 -0
- package/templates/apps/web/app/[locale]/software/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +39 -0
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/testimonials/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +63 -0
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +27 -0
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +34 -0
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +39 -0
- package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +152 -0
- package/templates/apps/web/app/api/auth/[...all]/route.ts +16 -0
- package/templates/apps/web/app/error-fallback.tsx +75 -0
- package/templates/apps/web/app/error.tsx +23 -0
- package/templates/apps/web/app/layout.tsx +18 -0
- package/templates/apps/web/app/llms.txt/route.ts +101 -0
- package/templates/apps/web/app/not-found.tsx +48 -0
- package/templates/apps/web/app/og/__tests__/route.test.ts +164 -0
- package/templates/apps/web/app/og/route.tsx +135 -0
- package/templates/apps/web/app/robots.txt/route.ts +36 -0
- package/templates/apps/web/app/sitemap.xml/route.ts +146 -0
- package/templates/apps/web/auth.ts +141 -0
- package/templates/apps/web/components/ThemeToggle.tsx +60 -0
- package/templates/apps/web/components/google-analytics.tsx +34 -0
- package/templates/apps/web/content/en/blog/index.mdx +15 -0
- package/templates/apps/web/content/en/blog/launching-ailk.mdx +20 -0
- package/templates/apps/web/content/en/docs/getting-started.mdx +30 -0
- package/templates/apps/web/i18n/request.ts +36 -0
- package/templates/apps/web/jest.config.cjs +100 -0
- package/templates/apps/web/lib/__mocks__/next-mdx-remote-rsc.js +12 -0
- package/templates/apps/web/lib/__mocks__/source-server.js +31 -0
- package/templates/apps/web/lib/__tests__/og.test.ts +65 -0
- package/templates/apps/web/lib/auth-env.ts +66 -0
- package/templates/apps/web/lib/blog-list-data.ts +95 -0
- package/templates/apps/web/lib/blog-post-data.ts +79 -0
- package/templates/apps/web/lib/nav-config.ts +13 -0
- package/templates/apps/web/lib/observability/better-stack.ts +27 -0
- package/templates/apps/web/lib/og.ts +23 -0
- package/templates/apps/web/lib/prisma-for-auth.ts +34 -0
- package/templates/apps/web/lib/source.ts +34 -0
- package/templates/apps/web/messages/en.json +131 -0
- package/templates/apps/web/middleware.ts +80 -0
- package/templates/apps/web/next.config.mjs +167 -0
- package/templates/apps/web/package.json +62 -0
- package/templates/apps/web/public/.well-known/security.txt +17 -0
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +4 -0
- package/templates/apps/web/public/logo-dark.svg +21 -0
- package/templates/apps/web/public/logo-light.svg +21 -0
- package/templates/apps/web/public/logomark-dark.svg +4 -0
- package/templates/apps/web/public/logomark-light.svg +4 -0
- package/templates/apps/web/public/og-default.png +0 -0
- package/templates/apps/web/public/site.webmanifest +19 -0
- package/templates/apps/web/scripts/assert-content-bundled.ts +55 -0
- package/templates/apps/web/security-headers.config.ts +132 -0
- package/templates/apps/web/sentry.client.config.ts +27 -0
- package/templates/apps/web/sentry.edge.config.ts +15 -0
- package/templates/apps/web/sentry.server.config.ts +20 -0
- package/templates/apps/web/source.config.ts +78 -0
- package/templates/apps/web/src/components/SiteNav.tsx +21 -0
- package/templates/apps/web/src/lib/routes.ts +37 -0
- package/templates/apps/web/tsconfig.json +35 -0
- package/templates/apps/web/types/messages.d.ts +19 -0
- package/templates/content/.gitkeep +0 -0
- package/templates/content/CLAUDE.md +85 -0
- package/templates/content/_site.mdx +38 -0
- package/templates/content/ar/HomePage/home.mdx +123 -0
- package/templates/content/ar/_site.mdx +35 -0
- package/templates/content/de/_site.mdx +29 -0
- package/templates/content/en/.gitkeep +0 -0
- package/templates/content/en/AboutPage/about.mdx +19 -0
- package/templates/content/en/AboutPage/about.schema.json +82 -0
- package/templates/content/en/BookCall/book-call.mdx +19 -0
- package/templates/content/en/BookCall/book-call.schema.json +121 -0
- package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +20 -0
- package/templates/content/en/CareersList/careers.mdx +24 -0
- package/templates/content/en/CaseStudiesList/case-studies.mdx +22 -0
- package/templates/content/en/CaseStudiesList/showcase.mdx +8 -0
- package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +32 -0
- package/templates/content/en/CaseStudy/schema-builder-migration.mdx +41 -0
- package/templates/content/en/ClientsList/clients.mdx +38 -0
- package/templates/content/en/ContactPage/contact.mdx +19 -0
- package/templates/content/en/ContactPage/contact.schema.json +100 -0
- package/templates/content/en/ContactPoint/primary.mdx +14 -0
- package/templates/content/en/Course/aeo-fundamentals.mdx +37 -0
- package/templates/content/en/Course/schema-builder-workshop.mdx +62 -0
- package/templates/content/en/CoursesList/courses.mdx +22 -0
- package/templates/content/en/DownloadsList/downloads.mdx +22 -0
- package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +39 -0
- package/templates/content/en/Event/ailk-launch-day-2026.mdx +35 -0
- package/templates/content/en/EventsList/events.mdx +26 -0
- package/templates/content/en/FAQPage/faq.mdx +420 -0
- package/templates/content/en/FAQPage/faq.schema.json +231 -0
- package/templates/content/en/GlossaryList/glossary.mdx +19 -0
- package/templates/content/en/GlossaryList/glossary.schema.json +300 -0
- package/templates/content/en/HomePage/home.mdx +312 -0
- package/templates/content/en/HomePage/home.schema.json +88 -0
- package/templates/content/en/HomePage/launch-service.mdx +475 -0
- package/templates/content/en/HomePage/launch-service.schema.json +138 -0
- package/templates/content/en/HowTo/add-mdx-content.mdx +38 -0
- package/templates/content/en/HowTo/configure-aeo-scoring.mdx +35 -0
- package/templates/content/en/HowTo/set-up-json-ld.mdx +36 -0
- package/templates/content/en/Integration/github-actions.mdx +31 -0
- package/templates/content/en/Integration/openai-api.mdx +26 -0
- package/templates/content/en/Integration/vercel.mdx +32 -0
- package/templates/content/en/IntegrationsList/integrations.mdx +22 -0
- package/templates/content/en/JobPosting/content-strategist.mdx +46 -0
- package/templates/content/en/JobPosting/developer-advocate.mdx +47 -0
- package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +57 -0
- package/templates/content/en/LandingPage/aeo-audit.mdx +19 -0
- package/templates/content/en/LandingPage/aeo-audit.schema.json +92 -0
- package/templates/content/en/LandingPage/agency-waitlist.mdx +340 -0
- package/templates/content/en/LandingPage/agency-waitlist.schema.json +152 -0
- package/templates/content/en/LandingPage/developers.mdx +19 -0
- package/templates/content/en/LandingPage/developers.schema.json +109 -0
- package/templates/content/en/LandingPage/for-agencies.mdx +390 -0
- package/templates/content/en/LandingPage/for-agencies.schema.json +107 -0
- package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +417 -0
- package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +105 -0
- package/templates/content/en/LandingPage/for-businesses.mdx +19 -0
- package/templates/content/en/LandingPage/for-businesses.schema.json +96 -0
- package/templates/content/en/LandingPage/platform.mdx +369 -0
- package/templates/content/en/LandingPage/platform.schema.json +166 -0
- package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +19 -0
- package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +102 -0
- package/templates/content/en/LandingPage/vs-wordpress.mdx +19 -0
- package/templates/content/en/LandingPage/vs-wordpress.schema.json +53 -0
- package/templates/content/en/NewsletterSignup/newsletter.mdx +236 -0
- package/templates/content/en/NewsletterSignup/newsletter.schema.json +91 -0
- package/templates/content/en/Organization/site-owner.mdx +16 -0
- package/templates/content/en/Partner/prisma.mdx +31 -0
- package/templates/content/en/Partner/supabase.mdx +30 -0
- package/templates/content/en/Partner/vercel.mdx +28 -0
- package/templates/content/en/PartnersList/partners.mdx +22 -0
- package/templates/content/en/Person/founder.mdx +18 -0
- package/templates/content/en/Pricing/pricing.mdx +538 -0
- package/templates/content/en/Pricing/pricing.schema.json +209 -0
- package/templates/content/en/PrivacyPage/privacy.mdx +165 -0
- package/templates/content/en/PrivacyPage/privacy.schema.json +64 -0
- package/templates/content/en/Product/ailk.mdx +19 -0
- package/templates/content/en/Product/ailk.schema.json +94 -0
- package/templates/content/en/Product/api-launch-kit.mdx +26 -0
- package/templates/content/en/Product/launch-kit.mdx +28 -0
- package/templates/content/en/ProductsList/products.mdx +26 -0
- package/templates/content/en/Project/ailk-phase-2.mdx +54 -0
- package/templates/content/en/ProjectsList/projects.mdx +25 -0
- package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +27 -0
- package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +28 -0
- package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +39 -0
- package/templates/content/en/ResourcesHub/resources.mdx +22 -0
- package/templates/content/en/Service/consulting.mdx +22 -0
- package/templates/content/en/Service/implementation.mdx +30 -0
- package/templates/content/en/Service/launch-service.mdx +232 -0
- package/templates/content/en/Service/launch.mdx +228 -0
- package/templates/content/en/ServicesList/services.mdx +84 -0
- package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +44 -0
- package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +47 -0
- package/templates/content/en/SoftwareProduct/ailk-schema.mdx +49 -0
- package/templates/content/en/TeamList/team.mdx +63 -0
- package/templates/content/en/TeamMember/founder.mdx +72 -0
- package/templates/content/en/TermsPage/terms.mdx +19 -0
- package/templates/content/en/TermsPage/terms.schema.json +99 -0
- package/templates/content/en/TestimonialsList/testimonials.mdx +44 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +30 -0
- package/templates/content/en/Whitepaper/ax-first-product-development.mdx +27 -0
- package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +27 -0
- package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +31 -0
- package/templates/content/en-XA/AboutPage/about.mdx +8 -0
- package/templates/content/en-XA/FAQPage/faq.mdx +8 -0
- package/templates/content/en-XA/HomePage/home.mdx +8 -0
- package/templates/content/es/_site.mdx +30 -0
- package/templates/content/fr/_site.mdx +30 -0
- package/templates/content/ja/_site.mdx +30 -0
- package/templates/content/pt-BR/AboutPage/about.mdx +8 -0
- package/templates/content/pt-BR/FAQPage/faq.mdx +8 -0
- package/templates/content/pt-BR/HomePage/home.mdx +23 -0
- package/templates/database/CHANGELOG.md +5 -0
- package/templates/database/README.md +234 -0
- package/templates/database/__tests__/user-cascade.test.ts +125 -0
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +3 -0
- package/templates/database/inbox/__tests__/schema.test.ts +40 -0
- package/templates/database/inbox/schema.prisma +52 -0
- package/templates/database/jest.config.cjs +29 -0
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +328 -0
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +100 -0
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +28 -0
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +25 -0
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +42 -0
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +67 -0
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +21 -0
- package/templates/database/migrations/migration_lock.toml +3 -0
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/package.json +45 -0
- package/templates/database/prisma.config.ts +20 -0
- package/templates/database/schema.prisma +487 -0
- package/templates/database/scripts/db-generate-locked.sh +80 -0
- package/templates/database/seed-test.ts +85 -0
- package/templates/database/tsconfig.build.json +16 -0
- package/templates/database/tsconfig.json +9 -0
- package/templates/package.json +125 -0
- package/templates/pnpm-workspace.yaml +6 -0
- package/templates/project.yaml +59 -0
- package/templates/tsconfig.base.json +18 -0
- package/templates/tsconfig.json +45 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML snapshot regression tests for the events routes →
|
|
3
|
+
* @working-theory/page-renderer renderPage({ bodyMode: "mdx-body" }) migration.
|
|
4
|
+
*
|
|
5
|
+
* Two-commit strategy:
|
|
6
|
+
* Commit 1: snapshots captured against pre-migration routes (baseline).
|
|
7
|
+
* Commit 2: routes migrated; same snapshots verify diff=0.
|
|
8
|
+
*
|
|
9
|
+
* Routes covered:
|
|
10
|
+
* - events/page.tsx (empty + populated — 2 snapshots)
|
|
11
|
+
* - events/[slug]/page.tsx (populated + notFound — 2 snapshots)
|
|
12
|
+
*
|
|
13
|
+
* Failure = snapshot diff > 0 bytes. DO NOT regenerate snapshots to pass.
|
|
14
|
+
*/
|
|
15
|
+
import React from "react";
|
|
16
|
+
import { render } from "@testing-library/react";
|
|
17
|
+
|
|
18
|
+
// ── next-mdx-remote/rsc mock ──────────────────────────────────────────────────
|
|
19
|
+
jest.mock("next-mdx-remote/rsc", () => ({
|
|
20
|
+
MDXRemote: ({ source }: { source: string }): React.ReactElement =>
|
|
21
|
+
React.createElement("div", { "data-testid": "mdx-rendered" }, source),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// ── next/navigation mock ──────────────────────────────────────────────────────
|
|
25
|
+
jest.mock("next/navigation", () => ({
|
|
26
|
+
__esModule: true,
|
|
27
|
+
notFound: jest.fn(() => {
|
|
28
|
+
throw new Error("NEXT_NOT_FOUND");
|
|
29
|
+
}),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
// ── @working-theory/ui mock ─────────────────────────────────────────────────────────────
|
|
33
|
+
jest.mock("@working-theory/ui", () => ({
|
|
34
|
+
SectionRenderer: ({ sections }: { sections: unknown[] }) =>
|
|
35
|
+
React.createElement("div", {
|
|
36
|
+
"data-testid": "section-renderer",
|
|
37
|
+
"data-section-count": String(sections.length),
|
|
38
|
+
}),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
import EventsPage from "../page";
|
|
42
|
+
import EventSlugPage from "../[slug]/page";
|
|
43
|
+
import { notFound } from "next/navigation";
|
|
44
|
+
|
|
45
|
+
const mockNotFound = notFound as jest.MockedFunction<typeof notFound>;
|
|
46
|
+
|
|
47
|
+
const getMdxSourceEmpty = jest.fn().mockResolvedValue(null);
|
|
48
|
+
|
|
49
|
+
// ── EventsPage ─────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
describe("EventsPage — snapshot regression", () => {
|
|
52
|
+
it("empty state renders deterministic HTML", async () => {
|
|
53
|
+
const element = await EventsPage({
|
|
54
|
+
params: Promise.resolve({ locale: "en" }),
|
|
55
|
+
getMdxSource: getMdxSourceEmpty,
|
|
56
|
+
});
|
|
57
|
+
const { container } = render(element);
|
|
58
|
+
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
59
|
+
`"<main><h1>Events</h1><p data-testid="section-empty-state">Events listing coming soon.</p></main>"`,
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("populated state renders deterministic HTML", async () => {
|
|
64
|
+
const getMdxSourcePopulated = jest.fn().mockResolvedValue({
|
|
65
|
+
frontmatter: {
|
|
66
|
+
type: "EventsList",
|
|
67
|
+
slug: "events",
|
|
68
|
+
title: "Events",
|
|
69
|
+
description: "Upcoming and past events.",
|
|
70
|
+
},
|
|
71
|
+
body: "# Events\n\nBody.",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const element = await EventsPage({
|
|
75
|
+
params: Promise.resolve({ locale: "en" }),
|
|
76
|
+
getMdxSource: getMdxSourcePopulated,
|
|
77
|
+
});
|
|
78
|
+
const { container } = render(element);
|
|
79
|
+
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
80
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Events","description":"Upcoming and past events.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Event"}]}}</script><main><h1>Events</h1><p data-testid="events-description">Upcoming and past events.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-rendered"># Events
|
|
81
|
+
|
|
82
|
+
Body.</div></article></div></div></main>"
|
|
83
|
+
`);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ── EventSlugPage ──────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
describe("EventSlugPage — snapshot regression", () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
mockNotFound.mockClear();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("populated state renders deterministic HTML", async () => {
|
|
95
|
+
const getMdxSourcePopulated = jest.fn().mockResolvedValue({
|
|
96
|
+
frontmatter: {
|
|
97
|
+
type: "Event",
|
|
98
|
+
slug: "annual-summit",
|
|
99
|
+
title: "Annual Summit",
|
|
100
|
+
description: "Our annual developer summit.",
|
|
101
|
+
},
|
|
102
|
+
body: "# Annual Summit\n\nBody.",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const element = await EventSlugPage({
|
|
106
|
+
params: Promise.resolve({ locale: "en", slug: "annual-summit" }),
|
|
107
|
+
getMdxSource: getMdxSourcePopulated,
|
|
108
|
+
});
|
|
109
|
+
const { container } = render(element);
|
|
110
|
+
// After migration: renderPage defaults populatedDescriptionTestId to
|
|
111
|
+
// "${slug}-description" where slug is the runtime params.slug value.
|
|
112
|
+
// This replaces the pre-migration hardcoded "event-description" testid
|
|
113
|
+
// with the slug-derived "annual-summit-description". Intentional per spec.
|
|
114
|
+
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
115
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Annual Summit","description":"Our annual developer summit.","mainEntity":{"@context":"https://schema.org","@type":"Event","name":"Annual Summit","description":"Our annual developer summit."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Events","item":"http://localhost:3000/en/events"},{"@type":"ListItem","position":3,"name":"Annual Summit","item":"http://localhost:3000/en/events/annual-summit"}]}]}</script><main><h1>Annual Summit</h1><p data-testid="annual-summit-description">Our annual developer summit.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-rendered"># Annual Summit
|
|
116
|
+
|
|
117
|
+
Body.</div></article></div></div></main>"
|
|
118
|
+
`);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("null source calls notFound()", async () => {
|
|
122
|
+
await expect(
|
|
123
|
+
EventSlugPage({
|
|
124
|
+
params: Promise.resolve({ locale: "en", slug: "unknown-event" }),
|
|
125
|
+
getMdxSource: getMdxSourceEmpty,
|
|
126
|
+
}),
|
|
127
|
+
).rejects.toThrow("NEXT_NOT_FOUND");
|
|
128
|
+
expect(mockNotFound).toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Events list page. Reads EventsList MDX via renderPage and emits
|
|
2
|
+
// a JSON-LD CollectionPage via the dispatcher.
|
|
3
|
+
import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
|
|
4
|
+
import { renderPage } from "@working-theory/page-renderer";
|
|
5
|
+
import type { ReactElement } from "react";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
params: Promise<{ locale: string }>;
|
|
9
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default async function Page({
|
|
13
|
+
params,
|
|
14
|
+
getMdxSource,
|
|
15
|
+
}: Props): Promise<ReactElement> {
|
|
16
|
+
const { locale } = await params;
|
|
17
|
+
return renderPage({
|
|
18
|
+
type: "EventsList",
|
|
19
|
+
slug: "events",
|
|
20
|
+
locale,
|
|
21
|
+
translationsNamespace: "events",
|
|
22
|
+
bodyMode: "mdx-body",
|
|
23
|
+
...(getMdxSource !== undefined && { getMdxSource }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// FAQ page. Reads the FAQPage MDX frontmatter, renders sections via
|
|
2
|
+
// SectionRenderer, and emits a JSON-LD <script> with mainEntity from
|
|
3
|
+
// the schema: override field (populated from MDX frontmatter).
|
|
4
|
+
import type {
|
|
5
|
+
getMdxSource as defaultGetMdxSource,
|
|
6
|
+
getPageSections as defaultGetPageSections} from "@working-theory/page-renderer";
|
|
7
|
+
import {
|
|
8
|
+
renderPage
|
|
9
|
+
} from "@working-theory/page-renderer";
|
|
10
|
+
import type { ReactElement } from "react";
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
params: Promise<{ locale: string }>;
|
|
14
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
15
|
+
getPageSections?: typeof defaultGetPageSections;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default async function Page({
|
|
19
|
+
params,
|
|
20
|
+
getMdxSource,
|
|
21
|
+
getPageSections,
|
|
22
|
+
}: Props): Promise<ReactElement> {
|
|
23
|
+
const { locale } = await params;
|
|
24
|
+
return renderPage({
|
|
25
|
+
type: "FAQPage",
|
|
26
|
+
slug: "faq",
|
|
27
|
+
locale,
|
|
28
|
+
translationsNamespace: "faq",
|
|
29
|
+
emptyStateScaffoldJsonLd: {
|
|
30
|
+
"@context": "https://schema.org",
|
|
31
|
+
"@type": "FAQPage",
|
|
32
|
+
},
|
|
33
|
+
...(getMdxSource !== undefined && { getMdxSource }),
|
|
34
|
+
...(getPageSections !== undefined && { getPageSections }),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// HowTo detail page. Reads the HowTo MDX for the given slug, renders via
|
|
2
|
+
// @working-theory/page-renderer, and emits a JSON-LD <script>. Unknown slugs trigger 404.
|
|
3
|
+
import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
|
|
4
|
+
import { getMdxSlugs, renderPage } from "@working-theory/page-renderer";
|
|
5
|
+
import type { ReactElement } from "react";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
params: Promise<{ locale: string; slug: string }>;
|
|
9
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function generateStaticParams({
|
|
13
|
+
params,
|
|
14
|
+
}: {
|
|
15
|
+
params: { locale: string };
|
|
16
|
+
}): Promise<{ slug: string }[]> {
|
|
17
|
+
const slugs = await getMdxSlugs("HowTo", params.locale);
|
|
18
|
+
return slugs.map((slug) => ({ slug }));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default async function Page({
|
|
22
|
+
params,
|
|
23
|
+
getMdxSource,
|
|
24
|
+
}: Props): Promise<ReactElement> {
|
|
25
|
+
const { locale, slug } = await params;
|
|
26
|
+
return renderPage({
|
|
27
|
+
type: "HowTo",
|
|
28
|
+
slug,
|
|
29
|
+
locale,
|
|
30
|
+
bodyMode: "mdx-body",
|
|
31
|
+
populatedDescriptionTestId: "how-to-description",
|
|
32
|
+
...(getMdxSource !== undefined && { getMdxSource }),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML snapshot regression tests for how-to routes — @working-theory/page-renderer migration.
|
|
3
|
+
*
|
|
4
|
+
* Node version: captured against Node 22.x (same as CI).
|
|
5
|
+
* MDX seed content: DI-injected stub — no fs reads, deterministic.
|
|
6
|
+
*
|
|
7
|
+
* Two-commit strategy:
|
|
8
|
+
* Commit 1: this file added; snapshots captured against pre-migration routes.
|
|
9
|
+
* Commit 2: routes migrated; same snapshots verify diff=0.
|
|
10
|
+
*
|
|
11
|
+
* Routes covered:
|
|
12
|
+
* - how-to/[slug] (populated: populated state)
|
|
13
|
+
* - how-to/[slug] (notFound: unknown slug triggers notFound())
|
|
14
|
+
*
|
|
15
|
+
* Failure = snapshot diff > 0 bytes. DO NOT regenerate snapshots to pass.
|
|
16
|
+
* A non-zero diff indicates a behavior regression in the migration. Escalate.
|
|
17
|
+
*/
|
|
18
|
+
import React from "react";
|
|
19
|
+
import { render } from "@testing-library/react";
|
|
20
|
+
|
|
21
|
+
jest.mock("next-mdx-remote/rsc", () => ({
|
|
22
|
+
__esModule: true,
|
|
23
|
+
MDXRemote: ({ source }: { source: string }): React.ReactElement =>
|
|
24
|
+
React.createElement("div", { "data-testid": "mdx-rendered" }, source),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock("next/navigation", () => ({
|
|
28
|
+
__esModule: true,
|
|
29
|
+
notFound: jest.fn(() => {
|
|
30
|
+
throw new Error("NEXT_NOT_FOUND");
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
import HowToSlugPage from "../[slug]/page";
|
|
35
|
+
import { notFound } from "next/navigation";
|
|
36
|
+
|
|
37
|
+
const mockNotFound = notFound as jest.MockedFunction<typeof notFound>;
|
|
38
|
+
|
|
39
|
+
describe("how-to/[slug] — snapshot regression", () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
mockNotFound.mockClear();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("populated state renders deterministic HTML", async () => {
|
|
45
|
+
const getMdxSource = jest.fn().mockResolvedValue({
|
|
46
|
+
frontmatter: {
|
|
47
|
+
type: "HowTo" as const,
|
|
48
|
+
slug: "deploy-ailk-vercel",
|
|
49
|
+
title: "Deploy AILK to Vercel",
|
|
50
|
+
description: "Step-by-step guide to deploying AI Launch Kit on Vercel.",
|
|
51
|
+
},
|
|
52
|
+
body: "## Steps\n\nBody content.",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const element = await HowToSlugPage({
|
|
56
|
+
params: Promise.resolve({ locale: "en", slug: "deploy-ailk-vercel" }),
|
|
57
|
+
getMdxSource,
|
|
58
|
+
});
|
|
59
|
+
const { container } = render(element);
|
|
60
|
+
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
61
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Deploy AILK to Vercel","description":"Step-by-step guide to deploying AI Launch Kit on Vercel.","mainEntity":{"@type":"HowTo","name":"Deploy AILK to Vercel"}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"How-To Guides","item":"http://localhost:3000/en/how-to"},{"@type":"ListItem","position":3,"name":"Deploy AILK to Vercel","item":"http://localhost:3000/en/how-to/deploy-ailk-vercel"}]}]}</script><main><h1>Deploy AILK to Vercel</h1><p data-testid="how-to-description">Step-by-step guide to deploying AI Launch Kit on Vercel.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-rendered">## Steps
|
|
62
|
+
|
|
63
|
+
Body content.</div></article></div></div></main>"
|
|
64
|
+
`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("unknown slug triggers notFound()", async () => {
|
|
68
|
+
const getMdxSource = jest.fn().mockResolvedValue(null);
|
|
69
|
+
|
|
70
|
+
await expect(
|
|
71
|
+
HowToSlugPage({
|
|
72
|
+
params: Promise.resolve({ locale: "en", slug: "unknown-slug" }),
|
|
73
|
+
getMdxSource,
|
|
74
|
+
}),
|
|
75
|
+
).rejects.toThrow("NEXT_NOT_FOUND");
|
|
76
|
+
expect(mockNotFound).toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// How-To list page. Enumerates HowTo slugs via the content adapter and renders
|
|
2
|
+
// a directory listing. No dedicated list MDX type exists for HowTo; the list
|
|
3
|
+
// page is generated from the detail type's slug enumeration.
|
|
4
|
+
import { getMdxSlugs as defaultGetMdxSlugs } from "@working-theory/page-renderer";
|
|
5
|
+
import { getTranslations } from "next-intl/server";
|
|
6
|
+
import type { ReactElement } from "react";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
params: Promise<{ locale: string }>;
|
|
10
|
+
getMdxSlugs?: typeof defaultGetMdxSlugs;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default async function Page({
|
|
14
|
+
params,
|
|
15
|
+
getMdxSlugs = defaultGetMdxSlugs,
|
|
16
|
+
}: Props): Promise<ReactElement> {
|
|
17
|
+
const { locale } = await params;
|
|
18
|
+
const t = await getTranslations("how-to");
|
|
19
|
+
const slugs = await getMdxSlugs("HowTo", locale);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<main>
|
|
23
|
+
<h1>{t("heading")}</h1>
|
|
24
|
+
<p data-testid="how-to-description">
|
|
25
|
+
{t("description")}
|
|
26
|
+
</p>
|
|
27
|
+
{slugs.length === 0 ? (
|
|
28
|
+
<p data-testid="section-empty-state">{t("emptyState")}</p>
|
|
29
|
+
) : (
|
|
30
|
+
<ul data-testid="how-to-list">
|
|
31
|
+
{slugs.map((slug) => (
|
|
32
|
+
<li key={slug}>
|
|
33
|
+
<a href={`/${locale}/how-to/${slug}`}>{slug}</a>
|
|
34
|
+
</li>
|
|
35
|
+
))}
|
|
36
|
+
</ul>
|
|
37
|
+
)}
|
|
38
|
+
</main>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Integration detail page. Reads the Integration MDX for the given slug, renders
|
|
2
|
+
// via next-mdx-remote/rsc, and emits a JSON-LD <script>. Unknown slugs trigger 404.
|
|
3
|
+
import {
|
|
4
|
+
renderPage,
|
|
5
|
+
getMdxSource as defaultGetMdxSource,
|
|
6
|
+
getMdxSlugs,
|
|
7
|
+
} from "@working-theory/page-renderer";
|
|
8
|
+
import { notFound } from "next/navigation";
|
|
9
|
+
import type { ReactElement } from "react";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
params: Promise<{ locale: string; slug: string }>;
|
|
13
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function generateStaticParams({
|
|
17
|
+
params,
|
|
18
|
+
}: {
|
|
19
|
+
params: { locale: string };
|
|
20
|
+
}): Promise<{ slug: string }[]> {
|
|
21
|
+
const slugs = await getMdxSlugs("Integration", params.locale);
|
|
22
|
+
return slugs.map((slug) => ({ slug }));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default async function Page({
|
|
26
|
+
params,
|
|
27
|
+
getMdxSource = defaultGetMdxSource,
|
|
28
|
+
}: Props): Promise<ReactElement> {
|
|
29
|
+
const { locale, slug } = await params;
|
|
30
|
+
const source = await getMdxSource("Integration", slug, locale);
|
|
31
|
+
if (!source) {
|
|
32
|
+
notFound();
|
|
33
|
+
}
|
|
34
|
+
return renderPage({
|
|
35
|
+
type: "Integration",
|
|
36
|
+
slug,
|
|
37
|
+
locale,
|
|
38
|
+
translationsNamespace: "integrations",
|
|
39
|
+
bodyMode: "mdx-body",
|
|
40
|
+
populatedDescriptionTestId: "integration-description",
|
|
41
|
+
getMdxSource: async () => source,
|
|
42
|
+
});
|
|
43
|
+
}
|
package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML snapshot regression tests for the integrations catalog routes
|
|
3
|
+
* apps/web → @working-theory/page-renderer migration (Spec 1.5.1).
|
|
4
|
+
*
|
|
5
|
+
* Node version: captured against Node 22.x (same as CI).
|
|
6
|
+
* MDX seed content: DI-injected stub — no fs reads, deterministic across environments.
|
|
7
|
+
*
|
|
8
|
+
* Two-commit strategy (per spec §6):
|
|
9
|
+
* Commit 1: this file added; snapshots captured against pre-migration routes.
|
|
10
|
+
* Commit 2: routes migrated; same snapshots verify diff=0 (GREEN).
|
|
11
|
+
*
|
|
12
|
+
* Routes under test:
|
|
13
|
+
* - integrations/page.tsx (index — empty-state + populated)
|
|
14
|
+
* - integrations/[slug]/page.tsx (detail — populated + null→notFound)
|
|
15
|
+
*
|
|
16
|
+
* Failure = snapshot diff > 0 bytes. DO NOT regenerate snapshots to pass.
|
|
17
|
+
* A non-zero diff indicates a behavior regression in the migration. Escalate.
|
|
18
|
+
*/
|
|
19
|
+
import React from "react";
|
|
20
|
+
import { render } from "@testing-library/react";
|
|
21
|
+
|
|
22
|
+
jest.mock("next/navigation", () => ({
|
|
23
|
+
__esModule: true,
|
|
24
|
+
notFound: jest.fn(() => {
|
|
25
|
+
throw new Error("NEXT_NOT_FOUND");
|
|
26
|
+
}),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
import { notFound } from "next/navigation";
|
|
30
|
+
import IntegrationsPage from "../page";
|
|
31
|
+
import IntegrationSlugPage from "../[slug]/page";
|
|
32
|
+
|
|
33
|
+
const mockNotFound = notFound as jest.MockedFunction<typeof notFound>;
|
|
34
|
+
|
|
35
|
+
// ── Shared stub helpers ────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
function makeIntegrationsListSource() {
|
|
38
|
+
return {
|
|
39
|
+
frontmatter: {
|
|
40
|
+
type: "IntegrationsList" as const,
|
|
41
|
+
slug: "integrations",
|
|
42
|
+
title: "Integrations",
|
|
43
|
+
description: "First-party integrations for AI Launch Kit.",
|
|
44
|
+
},
|
|
45
|
+
body: "# Integrations\n\nBody.",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function makeIntegrationSource(slug: string) {
|
|
50
|
+
return {
|
|
51
|
+
frontmatter: {
|
|
52
|
+
type: "Integration" as const,
|
|
53
|
+
slug,
|
|
54
|
+
title: "OpenAI API",
|
|
55
|
+
description: "Connect AILK to OpenAI.",
|
|
56
|
+
},
|
|
57
|
+
body: "# OpenAI API\n\nBody.",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── integrations/page.tsx — index ─────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
describe("IntegrationsPage — snapshot regression", () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
mockNotFound.mockClear();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("empty state renders deterministic HTML", async () => {
|
|
69
|
+
const getMdxSourceEmpty = jest.fn().mockResolvedValue(null);
|
|
70
|
+
const element = await IntegrationsPage({
|
|
71
|
+
params: Promise.resolve({ locale: "en" }),
|
|
72
|
+
getMdxSource: getMdxSourceEmpty,
|
|
73
|
+
});
|
|
74
|
+
const { container } = render(element);
|
|
75
|
+
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
76
|
+
`"<main><h1>Integrations</h1><p data-testid="section-empty-state">Integrations listing coming soon.</p></main>"`,
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("populated state renders deterministic HTML", async () => {
|
|
81
|
+
const source = makeIntegrationsListSource();
|
|
82
|
+
const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
|
|
83
|
+
const element = await IntegrationsPage({
|
|
84
|
+
params: Promise.resolve({ locale: "en" }),
|
|
85
|
+
getMdxSource: getMdxSourcePopulated,
|
|
86
|
+
});
|
|
87
|
+
const { container } = render(element);
|
|
88
|
+
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
89
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Integrations","description":"First-party integrations for AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"SoftwareApplication"}]}}</script><main><h1>Integrations</h1><p data-testid="integrations-description">First-party integrations for AI Launch Kit.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source="# Integrations
|
|
90
|
+
|
|
91
|
+
Body."></div></article></div></div></main>"
|
|
92
|
+
`);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ── integrations/[slug]/page.tsx — detail ─────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
describe("IntegrationSlugPage — snapshot regression", () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
mockNotFound.mockClear();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("populated state renders deterministic HTML", async () => {
|
|
104
|
+
const slug = "openai-api";
|
|
105
|
+
const source = makeIntegrationSource(slug);
|
|
106
|
+
const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
|
|
107
|
+
const element = await IntegrationSlugPage({
|
|
108
|
+
params: Promise.resolve({ locale: "en", slug }),
|
|
109
|
+
getMdxSource: getMdxSourcePopulated,
|
|
110
|
+
});
|
|
111
|
+
const { container } = render(element);
|
|
112
|
+
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
113
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"OpenAI API","description":"Connect AILK to OpenAI.","mainEntity":{"@type":"SoftwareApplication","name":"OpenAI API"}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Integrations","item":"http://localhost:3000/en/integrations"},{"@type":"ListItem","position":3,"name":"OpenAI API","item":"http://localhost:3000/en/integrations/openai-api"}]}]}</script><main><h1>OpenAI API</h1><p data-testid="integration-description">Connect AILK to OpenAI.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source="# OpenAI API
|
|
114
|
+
|
|
115
|
+
Body."></div></article></div></div></main>"
|
|
116
|
+
`);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("null source calls notFound()", async () => {
|
|
120
|
+
const getMdxSourceNull = jest.fn().mockResolvedValue(null);
|
|
121
|
+
await expect(
|
|
122
|
+
IntegrationSlugPage({
|
|
123
|
+
params: Promise.resolve({ locale: "en", slug: "unknown-slug" }),
|
|
124
|
+
getMdxSource: getMdxSourceNull,
|
|
125
|
+
}),
|
|
126
|
+
).rejects.toThrow("NEXT_NOT_FOUND");
|
|
127
|
+
expect(mockNotFound).toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Integrations list page. Reads IntegrationsList MDX via the content adapter and
|
|
2
|
+
// emits a JSON-LD CollectionPage via the dispatcher.
|
|
3
|
+
import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
|
|
4
|
+
import { renderPage } from "@working-theory/page-renderer";
|
|
5
|
+
import type { ReactElement } from "react";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
params: Promise<{ locale: string }>;
|
|
9
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default async function Page({
|
|
13
|
+
params,
|
|
14
|
+
getMdxSource,
|
|
15
|
+
}: Props): Promise<ReactElement> {
|
|
16
|
+
const { locale } = await params;
|
|
17
|
+
return renderPage({
|
|
18
|
+
type: "IntegrationsList",
|
|
19
|
+
slug: "integrations",
|
|
20
|
+
locale,
|
|
21
|
+
translationsNamespace: "integrations",
|
|
22
|
+
bodyMode: "mdx-body",
|
|
23
|
+
...(getMdxSource !== undefined && { getMdxSource }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Launch Service page. Reads the Service/launch-service MDX frontmatter,
|
|
2
|
+
// renders sections via SectionRenderer, and emits a JSON-LD <script>.
|
|
3
|
+
import type {
|
|
4
|
+
getMdxSource as defaultGetMdxSource,
|
|
5
|
+
getPageSections as defaultGetPageSections} from "@working-theory/page-renderer";
|
|
6
|
+
import {
|
|
7
|
+
renderPage
|
|
8
|
+
} from "@working-theory/page-renderer";
|
|
9
|
+
import type { ReactElement } from "react";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
params: Promise<{ locale: string }>;
|
|
13
|
+
getMdxSource?: typeof defaultGetMdxSource;
|
|
14
|
+
getPageSections?: typeof defaultGetPageSections;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default async function Page({
|
|
18
|
+
params,
|
|
19
|
+
getMdxSource,
|
|
20
|
+
getPageSections,
|
|
21
|
+
}: Props): Promise<ReactElement> {
|
|
22
|
+
const { locale } = await params;
|
|
23
|
+
return renderPage({
|
|
24
|
+
type: "Service",
|
|
25
|
+
slug: "launch-service",
|
|
26
|
+
locale,
|
|
27
|
+
translationsNamespace: "launch-service",
|
|
28
|
+
...(getMdxSource !== undefined && { getMdxSource }),
|
|
29
|
+
...(getPageSections !== undefined && { getPageSections }),
|
|
30
|
+
});
|
|
31
|
+
}
|