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,110 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
This README ships with every site scaffolded by `pnpm create ailk@latest`.
|
|
3
|
+
|
|
4
|
+
Substitution markers — search-and-replace these or let `create-ailk`
|
|
5
|
+
substitute on first run:
|
|
6
|
+
|
|
7
|
+
{{PROJECT_NAME}} — the directory name you scaffolded into
|
|
8
|
+
{{COMPANY_NAME}} — the legal/marketing name of the entity behind the site
|
|
9
|
+
{{COMPANY_DOMAIN}} — the canonical domain for the site (e.g. acme.example)
|
|
10
|
+
-->
|
|
11
|
+
|
|
12
|
+
# {{PROJECT_NAME}}
|
|
13
|
+
|
|
14
|
+
Built with [AI Launch Kit](https://ailaunchkit.ai). AI Launch Kit (AILK) is an agent-first OSS template — every page emits clean schema.org JSON-LD, every server action has a matching MCP tool, and every install is one command. This site is yours; you can edit it like any other repo.
|
|
15
|
+
|
|
16
|
+
## Run
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm install
|
|
20
|
+
pnpm dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Open <http://localhost:3000>. The home page renders from `content/en/HomePage/home.mdx` with valid JSON-LD already in the `<head>`.
|
|
24
|
+
|
|
25
|
+
To verify everything is wired before committing or deploying:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm preflight # architecture + types + lint + build
|
|
29
|
+
pnpm build # production build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> Requires Node.js 20+ and `pnpm`. No vendor API keys are required for first boot — Stripe, Anthropic, and email-provider integrations are opt-in.
|
|
33
|
+
|
|
34
|
+
## Content
|
|
35
|
+
|
|
36
|
+
The `content/` directory is locale-prefixed: every page lives at `content/<locale>/<type>/<slug>.mdx`. The default locale is `en` (English); the AILK scaffold ships English content only. Each subdirectory under `content/en/` is a schema-typed page surface — HomePage, AboutPage, ContactPage, FAQPage, Products, Services, Projects, BlogList, BlogPosting, and friends. The filename is the slug.
|
|
37
|
+
|
|
38
|
+
To customize the home page, edit [`content/en/HomePage/home.mdx`](./content/en/HomePage/home.mdx). Save and reload the dev server. No build step.
|
|
39
|
+
|
|
40
|
+
To add a new page, copy an existing MDX file in the matching directory and edit the frontmatter (`type`, `slug`, `title`, `description`).
|
|
41
|
+
|
|
42
|
+
> `content/_site.mdx` stays at the top level (not under `content/en/`) — site identity is locale-blind.
|
|
43
|
+
|
|
44
|
+
## Theme
|
|
45
|
+
|
|
46
|
+
Design tokens live in [`packages/theme`](https://www.npmjs.com/package/@working-theory/theme) (consumed from npm; no source edits needed). Components in [`packages/ui`](https://www.npmjs.com/package/@working-theory/ui) read from those tokens.
|
|
47
|
+
|
|
48
|
+
To swap the brand color, override the `theme.brand` token at the consumer site by extending the theme in your root layout. Example:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// app/layout.tsx (sketch)
|
|
52
|
+
import { theme } from "@working-theory/theme";
|
|
53
|
+
|
|
54
|
+
const myTheme = {
|
|
55
|
+
...theme,
|
|
56
|
+
brand: { ...theme.brand, primary: "#0a84ff" },
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Apply `myTheme` via the provider you use to wrap children. The full extension surface is documented at <https://ailaunchkit.ai/theme>.
|
|
61
|
+
|
|
62
|
+
## Environment
|
|
63
|
+
|
|
64
|
+
Copy `.env.example` to `.env` and fill in only what you need. Most variables are optional. A complete reference:
|
|
65
|
+
|
|
66
|
+
| Variable | Purpose |
|
|
67
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `AILK_LICENSE_KEY` | Optional. JWT issued by ailaunchkit.ai; verified locally to unlock Pro features. Get one at <https://ailaunchkit.ai/pro>. |
|
|
69
|
+
| `NEXT_PUBLIC_STREAMING_AI_ENABLED` | Optional. Set to `true` to enable the opt-in streaming AI assist UI. Off by default. |
|
|
70
|
+
| `ANTHROPIC_API_KEY` | Required only when `NEXT_PUBLIC_STREAMING_AI_ENABLED=true`. Used by the AI assist surface only. |
|
|
71
|
+
| `AILK_LOCAL_LEAD_CAPTURE` | Optional. When `true`, leads from the contact form are written to a local file instead of an API. |
|
|
72
|
+
| `AILK_LEADS_API_URL` | Optional. Override the upstream that receives lead-capture submissions. |
|
|
73
|
+
|
|
74
|
+
See [`.env.example`](./.env.example) for the full template with comments. Per-app overrides (e.g. `apps/api/.env`) are honored alongside the root `.env`.
|
|
75
|
+
|
|
76
|
+
## AI assist (optional)
|
|
77
|
+
|
|
78
|
+
The site ships with an opt-in streaming AI assist. It is **off by default**. To turn it on, set `NEXT_PUBLIC_STREAMING_AI_ENABLED=true` in `.env` and provide an `ANTHROPIC_API_KEY`. The assist runs server-side so the API key never reaches the browser. With both unset, the chat UI is hidden and no AI traffic flows.
|
|
79
|
+
|
|
80
|
+
## Deploy
|
|
81
|
+
|
|
82
|
+
[](https://vercel.com/new/clone?repository-url=https://github.com/{{COMPANY_NAME}}/{{PROJECT_NAME}})
|
|
83
|
+
|
|
84
|
+
Or from the project root:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pnpm vercel
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The `vercel.json` files inside `apps/web` and `apps/api` configure each surface as an independent Vercel project. Connect both, set the env vars listed above, and ship.
|
|
91
|
+
|
|
92
|
+
## Upgrading
|
|
93
|
+
|
|
94
|
+
This site consumes versioned `@working-theory/*` packages from npm. To pull upstream changes:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pnpm update '@working-theory/*'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That's the entire upgrade flow. There is no `git pull upstream`, no codemod, no fork-and-merge. Breaking changes ship in major-version bumps of the relevant `@working-theory/*` package; release notes call them out explicitly.
|
|
101
|
+
|
|
102
|
+
If you need to override a section component, swap its registration in the section registry (see <https://ailaunchkit.ai/sections>). Framework code stays out of your tree.
|
|
103
|
+
|
|
104
|
+
## License & contributing
|
|
105
|
+
|
|
106
|
+
Your project — `{{PROJECT_NAME}}`, owned by {{COMPANY_NAME}} ({{COMPANY_DOMAIN}}) — is yours. Pick whatever license suits you.
|
|
107
|
+
|
|
108
|
+
The AI Launch Kit framework upstream is **Apache 2.0** — see <https://github.com/tailwind-ai/ai-launch-kit/blob/main/LICENSE>. Contributions are welcome at the upstream repo; start with the [contributing guide](https://github.com/tailwind-ai/ai-launch-kit/blob/main/CONTRIBUTING.md).
|
|
109
|
+
|
|
110
|
+
For the full handbook, examples, and Pro features: <https://ailaunchkit.ai>.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# AI Launch Kit API Configuration
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# Local-only backend:
|
|
5
|
+
# - Handles local auth (sessions, API tokens)
|
|
6
|
+
# - Reserves Stripe webhook secret for future use
|
|
7
|
+
|
|
8
|
+
# =============================================================================
|
|
9
|
+
# Server
|
|
10
|
+
# =============================================================================
|
|
11
|
+
NODE_ENV=development
|
|
12
|
+
PORT=3002
|
|
13
|
+
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# Frontend URL (for CORS)
|
|
16
|
+
# =============================================================================
|
|
17
|
+
FRONTEND_URL=http://localhost:3000
|
|
18
|
+
|
|
19
|
+
# =============================================================================
|
|
20
|
+
# Local Database (REQUIRED)
|
|
21
|
+
# =============================================================================
|
|
22
|
+
# For auth only: User, Session, ApiToken, etc.
|
|
23
|
+
DATABASE_URL="postgresql://user:password@localhost:5432/customer_template"
|
|
24
|
+
|
|
25
|
+
# =============================================================================
|
|
26
|
+
# Stripe (Optional)
|
|
27
|
+
# =============================================================================
|
|
28
|
+
# Consumed by apps/api/src/lib/stripe.ts via @working-theory/stripe-client wrapper.
|
|
29
|
+
# Use sandbox keys only — live-mode keys are rejected at runtime.
|
|
30
|
+
STRIPE_SECRET_KEY=sk_test_xxx
|
|
31
|
+
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
|
32
|
+
|
|
33
|
+
# =============================================================================
|
|
34
|
+
# Internal service-to-service auth (#935)
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# When set, POST /v1/checkout/sessions requires the X-Internal-Auth header
|
|
37
|
+
# to match this value. Unset for OSS standalone deployments (no gate applied).
|
|
38
|
+
# Must match OSS_API_INTERNAL_SECRET in apps-paid/marketing-api/.env.local.
|
|
39
|
+
# Generate with: openssl rand -hex 32
|
|
40
|
+
# OSS_API_INTERNAL_SECRET=your-shared-secret-here
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# apps/api — Fastify REST API
|
|
2
|
+
|
|
3
|
+
Inherits root `CLAUDE.md`. This file adds API-specific rules.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- Fastify, TypeScript
|
|
8
|
+
- Persistence: configurable adapter (default DB; file fallback so OSS works without DB config)
|
|
9
|
+
- Email: operator-notification stub at OSS; routing pipeline at Pro (in `@working-theory/*`)
|
|
10
|
+
|
|
11
|
+
## API ↔ MCP parity (hard rule)
|
|
12
|
+
|
|
13
|
+
Every endpoint here has a 1:1 MCP tool in `apps/mcp`. When you add, change, or remove an endpoint, update the MCP tool in the same PR. CI enforces — broken parity fails the build.
|
|
14
|
+
|
|
15
|
+
## Endpoint conventions
|
|
16
|
+
|
|
17
|
+
Routes are versioned under `/v1/*`:
|
|
18
|
+
|
|
19
|
+
- `/v1/leads/{submit,subscribe,callback}` — lead capture, newsletter subscription, callback request
|
|
20
|
+
- `/v1/content/{types,:type,:type/:slug}` — public content reads
|
|
21
|
+
- `/v1/auth/tokens`, `/v1/user/{profile,data/:key}` — local auth + user data
|
|
22
|
+
- `/v1/checkout/sessions` — start a Stripe checkout
|
|
23
|
+
|
|
24
|
+
AEO Layer 1 scoring runs locally as a CLI command (`ailk audit`), not an HTTP endpoint.
|
|
25
|
+
|
|
26
|
+
JSON in, JSON out. Validate inputs with `@working-theory/validation`. Response envelope:
|
|
27
|
+
|
|
28
|
+
- Success: `{ ok: true, data }`
|
|
29
|
+
- Error: `{ ok: false, error: { code, message } }`
|
|
30
|
+
|
|
31
|
+
Use HTTP status codes — never 200 with `ok: false`.
|
|
32
|
+
|
|
33
|
+
## Lead capture pipeline (OSS scope)
|
|
34
|
+
|
|
35
|
+
Capture → persist (DB or file) → notify operator → return success. That's the entire OSS pipeline.
|
|
36
|
+
|
|
37
|
+
Routing rules, enrichment, dedupe, webhook fan-out, CRM integration live in `@working-theory/lead-routing`, loaded via JWT entitlement check. Never hardcode routing logic here — the boundary is what makes paid additive.
|
|
38
|
+
|
|
39
|
+
## Persistence
|
|
40
|
+
|
|
41
|
+
- DB adapter is the default. File fallback exists for zero-config OSS use.
|
|
42
|
+
- Don't assume a DB is configured. Read `AILK_DB_URL` and degrade gracefully.
|
|
43
|
+
- Migrations in `database/migrations`. Never edit a shipped migration — add a new one.
|
|
44
|
+
|
|
45
|
+
## Error handling
|
|
46
|
+
|
|
47
|
+
- Never leak stack traces in responses. Log them, return a code.
|
|
48
|
+
- 4xx for client errors, 5xx for server errors.
|
|
49
|
+
- Errors emitted here must map to MCP error responses with the same code (parity).
|
|
50
|
+
|
|
51
|
+
## BYO Stripe
|
|
52
|
+
|
|
53
|
+
- `@working-theory/stripe-client` wraps Stripe. Credentials come from customer env vars.
|
|
54
|
+
- Don't assume Stripe is configured. Read at startup; if missing, disable commerce endpoints and log a warning.
|
|
55
|
+
- Never log secret keys, even at debug.
|
|
56
|
+
|
|
57
|
+
## What NOT to do
|
|
58
|
+
|
|
59
|
+
- Don't import `@working-theory/*` from `apps/api`. Paid features run via JWT checks inside paid packages.
|
|
60
|
+
- Don't add an endpoint without the matching MCP tool.
|
|
61
|
+
- Don't return 200 with `{ ok: false }`.
|
|
62
|
+
- Don't read MDX directly — use `@working-theory/content-adapters`.
|
|
63
|
+
- Don't put session state in `apps/api`. It's stateless.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel Serverless Function Handler for Customer Template Backend
|
|
3
|
+
* This file wraps the Fastify server for Vercel's serverless functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { logger } from '@working-theory/observability';
|
|
7
|
+
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
|
8
|
+
|
|
9
|
+
import type { build as buildServer } from '../src/server.js';
|
|
10
|
+
|
|
11
|
+
type FastifyServer = Awaited<ReturnType<typeof buildServer>>;
|
|
12
|
+
|
|
13
|
+
// Import the build function from server
|
|
14
|
+
async function getBuildFunction(): Promise<typeof buildServer> {
|
|
15
|
+
try {
|
|
16
|
+
const srcModule = await import('../src/server.js');
|
|
17
|
+
logger.info('[Vercel Handler] Using server from src/server.js');
|
|
18
|
+
return srcModule.build;
|
|
19
|
+
} catch (srcError) {
|
|
20
|
+
logger.error(
|
|
21
|
+
{
|
|
22
|
+
err: srcError instanceof Error ? srcError.message : String(srcError),
|
|
23
|
+
stack: srcError instanceof Error ? srcError.stack : undefined,
|
|
24
|
+
},
|
|
25
|
+
'[Vercel Handler] Failed to import server',
|
|
26
|
+
);
|
|
27
|
+
throw new Error(`Cannot find server module: ${srcError instanceof Error ? srcError.message : String(srcError)}`, { cause: srcError });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Initialize build function
|
|
32
|
+
const buildPromise = getBuildFunction();
|
|
33
|
+
let serverInstance: FastifyServer | null = null;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get or create the Fastify server instance
|
|
37
|
+
*/
|
|
38
|
+
async function getServer() {
|
|
39
|
+
if (!serverInstance) {
|
|
40
|
+
logger.info('[Vercel Handler] Building server instance...');
|
|
41
|
+
const startTime = Date.now();
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const buildTimeout = new Promise<never>((_, reject) => {
|
|
45
|
+
setTimeout(() => reject(new Error('Server build timeout after 10s')), 10000);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const build = await Promise.race([buildPromise, buildTimeout]);
|
|
49
|
+
logger.info('[Vercel Handler] Build function obtained, creating server...');
|
|
50
|
+
|
|
51
|
+
const initTimeout = new Promise<never>((_, reject) => {
|
|
52
|
+
setTimeout(() => reject(new Error('Server initialization timeout after 10s')), 10000);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
serverInstance = await Promise.race([build(), initTimeout]);
|
|
56
|
+
|
|
57
|
+
const buildTime = Date.now() - startTime;
|
|
58
|
+
logger.info({ buildTime }, '[Vercel Handler] Server built successfully');
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const buildTime = Date.now() - startTime;
|
|
61
|
+
logger.error({ err: error, buildTime }, '[Vercel Handler] Failed to build server');
|
|
62
|
+
throw new Error(`Server build failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return serverInstance;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Vercel serverless function handler
|
|
70
|
+
*/
|
|
71
|
+
export default async function handler(
|
|
72
|
+
req: VercelRequest,
|
|
73
|
+
res: VercelResponse
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
const timeout = setTimeout(() => {
|
|
76
|
+
if (!res.headersSent) {
|
|
77
|
+
logger.error('[Vercel Handler] Request timeout');
|
|
78
|
+
res.status(504).json({
|
|
79
|
+
error: 'Gateway Timeout',
|
|
80
|
+
message: 'Request took too long to process',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}, 25000);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
logger.info({ method: req.method, url: req.url }, '[Vercel Handler] Starting request');
|
|
87
|
+
|
|
88
|
+
const server = await getServer();
|
|
89
|
+
|
|
90
|
+
const url = new URL(req.url || '/', 'http://localhost');
|
|
91
|
+
const pathname = url.pathname;
|
|
92
|
+
|
|
93
|
+
logger.info({ method: req.method, pathname }, '[Vercel Handler] Processing request');
|
|
94
|
+
|
|
95
|
+
const fastifyRequest = {
|
|
96
|
+
method: req.method || 'GET',
|
|
97
|
+
url: pathname,
|
|
98
|
+
headers: req.headers as Record<string, string>,
|
|
99
|
+
body: req.body,
|
|
100
|
+
query: req.query as Record<string, string>,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const response = await server.inject(fastifyRequest);
|
|
104
|
+
|
|
105
|
+
logger.info({ status: response.statusCode }, '[Vercel Handler] Response status');
|
|
106
|
+
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
|
|
109
|
+
res.status(response.statusCode);
|
|
110
|
+
|
|
111
|
+
Object.entries(response.headers).forEach(([key, value]) => {
|
|
112
|
+
if (value !== undefined && !res.headersSent) {
|
|
113
|
+
const headerValue = Array.isArray(value)
|
|
114
|
+
? value.map((v) => String(v)).join(', ')
|
|
115
|
+
: String(value);
|
|
116
|
+
res.setHeader(key, headerValue);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!res.getHeader('Content-Type') && !res.headersSent) {
|
|
121
|
+
res.setHeader('Content-Type', 'application/json');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!res.headersSent) {
|
|
125
|
+
const body = typeof response.body === 'string'
|
|
126
|
+
? response.body
|
|
127
|
+
: JSON.stringify(response.body);
|
|
128
|
+
|
|
129
|
+
res.send(body);
|
|
130
|
+
logger.info('[Vercel Handler] Response sent successfully');
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
clearTimeout(timeout);
|
|
134
|
+
|
|
135
|
+
logger.error({ err: error }, '[Vercel Handler] Error handling request');
|
|
136
|
+
|
|
137
|
+
if (!res.headersSent) {
|
|
138
|
+
res.status(500).json({
|
|
139
|
+
error: 'Internal Server Error',
|
|
140
|
+
message: error instanceof Error ? error.message : String(error),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
roots: ['<rootDir>/src'],
|
|
5
|
+
testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(test).ts'],
|
|
6
|
+
testPathIgnorePatterns: ['/node_modules/', '.*\\.spec\\.ts$'],
|
|
7
|
+
// Ignore node_modules except jose (ESM-only, loaded transitively via @working-theory/license → @working-theory/schema-pack-registry → @working-theory/schema).
|
|
8
|
+
transformIgnorePatterns: ['/node_modules/(?!(\\.pnpm/)?jose@?[^/]*?(/|$))'],
|
|
9
|
+
moduleFileExtensions: ['ts', 'js', 'json'],
|
|
10
|
+
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
|
|
11
|
+
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/__tests__/**'],
|
|
12
|
+
// Use v8 coverage provider for better async code tracking (route handlers via Fastify inject)
|
|
13
|
+
coverageProvider: 'v8',
|
|
14
|
+
coverageThreshold: {
|
|
15
|
+
global: {
|
|
16
|
+
lines: 85,
|
|
17
|
+
functions: 88, // Lowered from 90% due to technical limitations (index.ts, prisma.ts, stripe.ts preParsing hook)
|
|
18
|
+
branches: 85,
|
|
19
|
+
statements: 85,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
moduleNameMapper: {
|
|
23
|
+
// Mock server-only in Jest environments — the package throws by design
|
|
24
|
+
// outside Next.js. The real guard runs in production Next.js builds.
|
|
25
|
+
'^server-only$': '<rootDir>/../../__mocks__/server-only.ts',
|
|
26
|
+
// Map Prisma client import for testing (must come before the generic .js stripper).
|
|
27
|
+
// Phase C (#710): the v7 prisma-client generator emits TypeScript source files
|
|
28
|
+
// (client.ts) that use `import.meta.url`, which Jest's CommonJS preset can't
|
|
29
|
+
// load directly. apps/api now imports from @working-theory/database; tests mock both
|
|
30
|
+
// forms to the same shared mock.
|
|
31
|
+
'^@working-theory/database$': '<rootDir>/src/lib/__mocks__/prisma-client.js',
|
|
32
|
+
'^.*database/generated/client/(index|client)\\.(js|ts)$': '<rootDir>/src/lib/__mocks__/prisma-client.js',
|
|
33
|
+
// Map workspace packages to their TypeScript source so ts-jest compiles them
|
|
34
|
+
// directly (the published dist is ESM, which Jest's CJS preset can't load).
|
|
35
|
+
'^@working-theory/analytics/server$': '<rootDir>/../../packages/analytics/src/server/index.ts',
|
|
36
|
+
'^@vercel/analytics/server$': '<rootDir>/../../packages/analytics/__mocks__/vercel-analytics-server.ts',
|
|
37
|
+
'^@working-theory/common$': '<rootDir>/../../packages/common/src/index.ts',
|
|
38
|
+
'^@working-theory/content-adapters$': '<rootDir>/../../packages/content-adapters/src/index.ts',
|
|
39
|
+
'^@working-theory/content-projection$': '<rootDir>/../../packages/content-projection/src/index.ts',
|
|
40
|
+
'^@working-theory/content-projection/node$': '<rootDir>/../../packages/content-projection/src/node.ts',
|
|
41
|
+
'^@working-theory/email$': '<rootDir>/../../packages/email/src/index.ts',
|
|
42
|
+
'^@working-theory/license$': '<rootDir>/../../packages/license/src/index.ts',
|
|
43
|
+
'^@working-theory/observability$': '<rootDir>/../../packages/observability/src/index.ts',
|
|
44
|
+
'^@working-theory/schema/faq$': '<rootDir>/../../packages/schema/src/faq.ts',
|
|
45
|
+
'^@working-theory/schema$': '<rootDir>/../../packages/schema/src/index.ts',
|
|
46
|
+
'^@working-theory/schema-pack-registry$': '<rootDir>/../../packages/schema-pack-registry/src/index.ts',
|
|
47
|
+
'^@working-theory/stripe-client$': '<rootDir>/../../packages/stripe-client/src/index.ts',
|
|
48
|
+
'^@working-theory/validation$': '<rootDir>/../../packages/validation/src/index.ts',
|
|
49
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
50
|
+
},
|
|
51
|
+
transform: {
|
|
52
|
+
'^.+\\.(tsx?|jsx?|mjs)$': [
|
|
53
|
+
'ts-jest',
|
|
54
|
+
{
|
|
55
|
+
tsconfig: {
|
|
56
|
+
module: 'commonjs', // Jest runs in CommonJS
|
|
57
|
+
target: 'ES2020',
|
|
58
|
+
lib: ['ES2020'],
|
|
59
|
+
allowJs: true,
|
|
60
|
+
declaration: false,
|
|
61
|
+
strict: true,
|
|
62
|
+
esModuleInterop: true,
|
|
63
|
+
skipLibCheck: true,
|
|
64
|
+
ignoreDeprecations: '6.0',
|
|
65
|
+
forceConsistentCasingInFileNames: true,
|
|
66
|
+
},
|
|
67
|
+
// Allow import.meta syntax to be parsed (source is ESM, but we compile to CommonJS)
|
|
68
|
+
diagnostics: {
|
|
69
|
+
ignoreCodes: [1343], // Ignore "import.meta only allowed in ESM modules" error
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@working-theory/api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI Launch Kit API — Fastify routes + services",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "tsx watch src/cli.ts",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"build:vercel": "cd ../../database && pnpm exec prisma generate && cd ../.. && pnpm --filter @working-theory/validation build && pnpm --filter @working-theory/api build && mkdir -p apps/api/public && echo '{}' > apps/api/public/.vercel",
|
|
12
|
+
"start": "node dist/cli.js",
|
|
13
|
+
"lint": "eslint src --ext .ts",
|
|
14
|
+
"test": "jest",
|
|
15
|
+
"test:watch": "jest --watch",
|
|
16
|
+
"test:coverage": "jest --coverage",
|
|
17
|
+
"db:generate": "cd ../../database && prisma generate",
|
|
18
|
+
"db:push": "cd ../../database && prisma db push",
|
|
19
|
+
"db:migrate": "cd ../../database && prisma migrate dev",
|
|
20
|
+
"db:studio": "cd ../../database && prisma studio"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@working-theory/analytics": "workspace:*",
|
|
24
|
+
"@working-theory/common": "workspace:*",
|
|
25
|
+
"@working-theory/content-adapters": "workspace:*",
|
|
26
|
+
"@working-theory/content-projection": "workspace:*",
|
|
27
|
+
"@working-theory/database": "workspace:*",
|
|
28
|
+
"@working-theory/email": "workspace:*",
|
|
29
|
+
"@working-theory/license": "workspace:*",
|
|
30
|
+
"@working-theory/observability": "workspace:*",
|
|
31
|
+
"@working-theory/schema": "workspace:*",
|
|
32
|
+
"@working-theory/stripe-client": "workspace:*",
|
|
33
|
+
"@working-theory/validation": "workspace:*",
|
|
34
|
+
"@fastify/cors": "^11.2.0",
|
|
35
|
+
"@fastify/rate-limit": "^10.3.0",
|
|
36
|
+
"@prisma/adapter-pg": "^7.8.0",
|
|
37
|
+
"@prisma/client": "^7.8.0",
|
|
38
|
+
"dotenv": "^17.4.2",
|
|
39
|
+
"fastify": "^5.7.2",
|
|
40
|
+
"pg": "^8.22.0",
|
|
41
|
+
"zod": "^4.2.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/jest": "^30.0.0",
|
|
45
|
+
"@types/node": "^22.20.0",
|
|
46
|
+
"@types/pg": "^8.20.0",
|
|
47
|
+
"@vercel/node": "^5.8.22",
|
|
48
|
+
"jest": "^30.4.2",
|
|
49
|
+
"prisma": "^7.8.0",
|
|
50
|
+
"ts-jest": "^29.4.11",
|
|
51
|
+
"tsx": "^4.22.4",
|
|
52
|
+
"typescript": "^6.0.3"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file content-fixture.ts
|
|
3
|
+
* Filesystem fixture helpers for content route tests.
|
|
4
|
+
*
|
|
5
|
+
* Creates a per-test temp directory with minimal MDX fixture content,
|
|
6
|
+
* points the content adapter at it via CONTENT_ROOT env, and cleans up
|
|
7
|
+
* in afterEach. This allows content route tests to exercise the real
|
|
8
|
+
* content adapter stack without mocking internal modules.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { setupContentFixture } from "../../__tests__/factories/content-fixture";
|
|
12
|
+
*
|
|
13
|
+
* const { getContentRoot } = setupContentFixture();
|
|
14
|
+
*
|
|
15
|
+
* The setup/teardown hooks register themselves — call setupContentFixture()
|
|
16
|
+
* at describe-scope and the beforeEach/afterEach run automatically.
|
|
17
|
+
*
|
|
18
|
+
* Issue #507: replaces jest.mock("../../../lib/content-adapter.js") in
|
|
19
|
+
* content route tests.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import * as fs from "fs/promises";
|
|
23
|
+
import * as os from "os";
|
|
24
|
+
import * as path from "path";
|
|
25
|
+
|
|
26
|
+
import { resetContentAdapter } from "../../lib/content-adapter.js";
|
|
27
|
+
|
|
28
|
+
export interface ContentFixture {
|
|
29
|
+
/** Returns the current temp content root (changes per test). */
|
|
30
|
+
getContentRoot: () => string;
|
|
31
|
+
/**
|
|
32
|
+
* Write an additional MDX file into the fixture dir.
|
|
33
|
+
* type and slug are used to construct the path: <root>/<type>/<slug>.mdx
|
|
34
|
+
*/
|
|
35
|
+
writeFixturePage: (
|
|
36
|
+
type: string,
|
|
37
|
+
slug: string,
|
|
38
|
+
frontmatter: Record<string, string>,
|
|
39
|
+
body?: string,
|
|
40
|
+
) => Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register beforeEach/afterEach hooks that create a fresh temp dir per test,
|
|
45
|
+
* set CONTENT_ROOT, and reset the adapter singleton.
|
|
46
|
+
*
|
|
47
|
+
* Returns a fixture handle for writing additional content from within tests.
|
|
48
|
+
*/
|
|
49
|
+
export function setupContentFixture(): ContentFixture {
|
|
50
|
+
let contentRoot = "";
|
|
51
|
+
|
|
52
|
+
beforeEach(async () => {
|
|
53
|
+
contentRoot = await fs.mkdtemp(
|
|
54
|
+
path.join(os.tmpdir(), "ailk-api-content-test-"),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Seed one fixture page per supported type so the adapter is not empty.
|
|
58
|
+
// Tests that need specific pages call writeFixturePage() themselves.
|
|
59
|
+
await Promise.all([
|
|
60
|
+
writeDefaultFixture(contentRoot, "HomePage", "home"),
|
|
61
|
+
writeDefaultFixture(contentRoot, "AboutPage", "about"),
|
|
62
|
+
writeDefaultFixture(contentRoot, "Product", "test-product"),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
process.env.CONTENT_ROOT = contentRoot;
|
|
66
|
+
resetContentAdapter();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(async () => {
|
|
70
|
+
delete process.env.CONTENT_ROOT;
|
|
71
|
+
resetContentAdapter();
|
|
72
|
+
|
|
73
|
+
// Best-effort cleanup — don't fail the test if temp dir can't be removed.
|
|
74
|
+
try {
|
|
75
|
+
await fs.rm(contentRoot, { recursive: true, force: true });
|
|
76
|
+
} catch {
|
|
77
|
+
// ignore
|
|
78
|
+
}
|
|
79
|
+
contentRoot = "";
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
getContentRoot: () => contentRoot,
|
|
84
|
+
writeFixturePage: (type, slug, frontmatter, body = "") =>
|
|
85
|
+
writeFixturePage(contentRoot, type, slug, frontmatter, body),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
async function writeDefaultFixture(
|
|
92
|
+
root: string,
|
|
93
|
+
type: string,
|
|
94
|
+
slug: string,
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
await writeFixturePage(
|
|
97
|
+
root,
|
|
98
|
+
type,
|
|
99
|
+
slug,
|
|
100
|
+
{
|
|
101
|
+
type,
|
|
102
|
+
slug,
|
|
103
|
+
title: `${type} ${slug} title`,
|
|
104
|
+
description: `${type} ${slug} description`,
|
|
105
|
+
},
|
|
106
|
+
`Default fixture body for ${type}/${slug}.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function writeFixturePage(
|
|
111
|
+
root: string,
|
|
112
|
+
type: string,
|
|
113
|
+
slug: string,
|
|
114
|
+
frontmatter: Record<string, string>,
|
|
115
|
+
body = "",
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
// Write the fixture in TWO places:
|
|
118
|
+
// 1. <root>/en/<type>/<slug>.mdx — post-S2 locale-prefixed read path
|
|
119
|
+
// (callers passing an explicit locale find pages here).
|
|
120
|
+
// 2. <root>/<type>/<slug>.mdx — legacy write path. The adapter's write
|
|
121
|
+
// methods (createPage/updatePage/deletePage) still write here in
|
|
122
|
+
// Phase 3 S3 per the S1 spec ("locale-aware writes deferred"), so
|
|
123
|
+
// write-route tests assert against this layout.
|
|
124
|
+
// Duplicating the fixture keeps both read and write test paths happy until
|
|
125
|
+
// write tools learn the locale-prefixed layout.
|
|
126
|
+
const fmLines = Object.entries(frontmatter)
|
|
127
|
+
.map(([k, v]) => `${k}: "${v}"`)
|
|
128
|
+
.join("\n");
|
|
129
|
+
const content = `---\n${fmLines}\n---\n\n${body}\n`;
|
|
130
|
+
|
|
131
|
+
const localeDir = path.join(root, "en", type);
|
|
132
|
+
await fs.mkdir(localeDir, { recursive: true });
|
|
133
|
+
await fs.writeFile(path.join(localeDir, `${slug}.mdx`), content, "utf8");
|
|
134
|
+
|
|
135
|
+
const legacyDir = path.join(root, type);
|
|
136
|
+
await fs.mkdir(legacyDir, { recursive: true });
|
|
137
|
+
await fs.writeFile(path.join(legacyDir, `${slug}.mdx`), content, "utf8");
|
|
138
|
+
}
|