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,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* readLocales — read and validate the `product.locales` field from project.yaml.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `apps/web/lib/locales.ts` and `apps/api/src/lib/locales.ts`.
|
|
5
|
+
* Duplicated because architecture.yaml does not permit cross-app imports
|
|
6
|
+
* (apps/mcp may not import apps/web or apps/api), and a shared package for
|
|
7
|
+
* filesystem helpers would extend the architectural surface beyond the scope
|
|
8
|
+
* of this spec.
|
|
9
|
+
*
|
|
10
|
+
* Validation rules:
|
|
11
|
+
* - `locales` field absent → defaults to `["en"]` (backward-compat with OSS
|
|
12
|
+
* forks that pre-date the field)
|
|
13
|
+
* - `locales` present → must be a non-empty array of BCP-47 locale tags
|
|
14
|
+
* matching `/^[a-z]{2}(-[A-Z]{2})?$/` (MVP subset)
|
|
15
|
+
* - `product.default_locale` must be included in the `locales` list; parse
|
|
16
|
+
* error if not
|
|
17
|
+
* - `locales: []` → parse error (empty array is invalid)
|
|
18
|
+
*
|
|
19
|
+
* Used by content read tools to validate the `locale` parameter against the
|
|
20
|
+
* configured allowlist before passing to the adapter (defense-in-depth per
|
|
21
|
+
* AC-i18n-07).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { readFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
|
|
27
|
+
import { parse } from "yaml";
|
|
28
|
+
|
|
29
|
+
/** BCP-47 MVP subset: `xx` or `xx-XX` (language + optional region). */
|
|
30
|
+
const BCP47_RE = /^[a-z]{2}(-[A-Z]{2})?$/;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reads, validates, and returns the sorted locale list from project.yaml.
|
|
34
|
+
*
|
|
35
|
+
* @param cwd - Directory to search from. Defaults to `process.cwd()`.
|
|
36
|
+
* @returns Sorted `string[]` of enabled locale tags.
|
|
37
|
+
* @throws Error with a descriptive message (file + cause) when validation fails.
|
|
38
|
+
*/
|
|
39
|
+
export function readLocales(cwd = process.cwd()): string[] {
|
|
40
|
+
const filePath = resolveProjectYamlPath(cwd);
|
|
41
|
+
|
|
42
|
+
let raw: string;
|
|
43
|
+
try {
|
|
44
|
+
raw = readFileSync(filePath, "utf8");
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const e = new Error(
|
|
47
|
+
`[locales] Cannot read ${filePath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
48
|
+
);
|
|
49
|
+
(e as { cause?: unknown }).cause = err;
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let doc: unknown;
|
|
54
|
+
try {
|
|
55
|
+
doc = parse(raw);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const e = new Error(
|
|
58
|
+
`[locales] YAML parse error in ${filePath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
59
|
+
);
|
|
60
|
+
(e as { cause?: unknown }).cause = err;
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!doc || typeof doc !== "object") {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`[locales] ${filePath}: expected a YAML mapping at the top level`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const top = doc as Record<string, unknown>;
|
|
71
|
+
const product = top["product"];
|
|
72
|
+
|
|
73
|
+
const p =
|
|
74
|
+
product && typeof product === "object"
|
|
75
|
+
? (product as Record<string, unknown>)
|
|
76
|
+
: {};
|
|
77
|
+
|
|
78
|
+
const defaultLocale =
|
|
79
|
+
typeof p["default_locale"] === "string" ? p["default_locale"] : "en";
|
|
80
|
+
|
|
81
|
+
const rawLocales = p["locales"];
|
|
82
|
+
|
|
83
|
+
if (rawLocales === undefined || rawLocales === null) {
|
|
84
|
+
return ["en"];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!Array.isArray(rawLocales)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`[locales] ${filePath}: product.locales must be an array, got ${typeof rawLocales}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (rawLocales.length === 0) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`[locales] ${filePath}: product.locales must be a non-empty array (got empty array)`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const entry of rawLocales) {
|
|
100
|
+
if (typeof entry !== "string") {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`[locales] ${filePath}: product.locales entries must be strings, got ${typeof entry}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!BCP47_RE.test(entry)) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`[locales] ${filePath}: product.locales entry "${entry}" is not a valid BCP-47 locale tag (expected format: xx or xx-XX)`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const locales = rawLocales as string[];
|
|
113
|
+
|
|
114
|
+
if (!locales.includes(defaultLocale)) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`[locales] ${filePath}: product.default_locale "${defaultLocale}" is not in product.locales [${locales.join(", ")}]`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return [...locales].sort();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Read the project's default locale from project.yaml. Returns `"en"` when
|
|
125
|
+
* project.yaml is absent or `product.default_locale` is unset — same default
|
|
126
|
+
* as the adapter.
|
|
127
|
+
*/
|
|
128
|
+
export function readDefaultLocale(cwd = process.cwd()): string {
|
|
129
|
+
const filePath = resolveProjectYamlPath(cwd);
|
|
130
|
+
|
|
131
|
+
let raw: string;
|
|
132
|
+
try {
|
|
133
|
+
raw = readFileSync(filePath, "utf8");
|
|
134
|
+
} catch {
|
|
135
|
+
return "en";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let doc: unknown;
|
|
139
|
+
try {
|
|
140
|
+
doc = parse(raw);
|
|
141
|
+
} catch {
|
|
142
|
+
return "en";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!doc || typeof doc !== "object") return "en";
|
|
146
|
+
const product = (doc as Record<string, unknown>)["product"];
|
|
147
|
+
if (!product || typeof product !== "object") return "en";
|
|
148
|
+
const defaultLocale = (product as Record<string, unknown>)["default_locale"];
|
|
149
|
+
return typeof defaultLocale === "string" ? defaultLocale : "en";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Probe likely locations for project.yaml. Apps run from their own dirs in
|
|
154
|
+
* dev/build (`apps/mcp/`) but from repo root in some test setups.
|
|
155
|
+
*/
|
|
156
|
+
function resolveProjectYamlPath(cwd: string): string {
|
|
157
|
+
const candidates = [
|
|
158
|
+
join(cwd, "project.yaml"),
|
|
159
|
+
join(cwd, "..", "project.yaml"),
|
|
160
|
+
join(cwd, "..", "..", "project.yaml"),
|
|
161
|
+
];
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
try {
|
|
164
|
+
readFileSync(candidate, "utf8");
|
|
165
|
+
return candidate;
|
|
166
|
+
} catch {
|
|
167
|
+
// continue
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return candidates[0] ?? join(cwd, "project.yaml");
|
|
171
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paid-tier license guard for apps/mcp content-write tools (D14a Stage 0).
|
|
3
|
+
*
|
|
4
|
+
* Calls `checkLicense()` from @working-theory/license and returns a canonical
|
|
5
|
+
* `payment_required` error body for free tier; returns `null` to signal the
|
|
6
|
+
* caller may proceed.
|
|
7
|
+
*
|
|
8
|
+
* Usage in a tool handler:
|
|
9
|
+
*
|
|
10
|
+
* const guard = await requirePaidTier();
|
|
11
|
+
* if (guard) return guard; // free tier — return canonical error body
|
|
12
|
+
* // paid tier — proceed
|
|
13
|
+
*
|
|
14
|
+
* Pattern intentionally returns the error body directly (vs. throwing) — MCP
|
|
15
|
+
* tools surface errors as their result body, not via thrown exceptions.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { checkLicense } from "@working-theory/license";
|
|
19
|
+
|
|
20
|
+
import { paymentRequiredError, type ErrorBody } from "./errors.js";
|
|
21
|
+
|
|
22
|
+
const FREE_TIER_MESSAGE =
|
|
23
|
+
"This tool requires a paid license. Visit https://ailaunchkit.ai to upgrade.";
|
|
24
|
+
|
|
25
|
+
export async function requirePaidTier(): Promise<ErrorBody | null> {
|
|
26
|
+
let result;
|
|
27
|
+
try {
|
|
28
|
+
result = await checkLicense();
|
|
29
|
+
} catch {
|
|
30
|
+
// Per ADR 0014 — local-verify license; an exception is a bug, not a
|
|
31
|
+
// network failure. Treat as free for safe-by-default gating.
|
|
32
|
+
return paymentRequiredError(FREE_TIER_MESSAGE);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Revoked paid licenses keep verifying until JWT exp (up to 24h after the
|
|
36
|
+
// server reports `revoked`). Closing the gate when `revoked === true`
|
|
37
|
+
// shrinks that window to "next request" without waiting for exp. See
|
|
38
|
+
// packages/license/src/types.ts § LicenseResult and ADR 0014.
|
|
39
|
+
if (result.revoked === true || result.tier === "free") {
|
|
40
|
+
return paymentRequiredError(FREE_TIER_MESSAGE);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolveLocale — validate a caller-supplied locale against the project's
|
|
3
|
+
* allowlist. Phase 3 S3.
|
|
4
|
+
*
|
|
5
|
+
* Returns the requested locale when it's present in `readLocales()`;
|
|
6
|
+
* otherwise silently falls back to the project's default locale (AC-i18n-07).
|
|
7
|
+
*
|
|
8
|
+
* Failures reading `project.yaml` degrade to a default-locale-only allowlist
|
|
9
|
+
* — mirrors the same defensive behavior in `apps/api/src/routes/content/*`.
|
|
10
|
+
*
|
|
11
|
+
* This boundary check is defense-in-depth on top of the adapter's BCP-47
|
|
12
|
+
* regex; the adapter still applies its own check and fallback chain on
|
|
13
|
+
* every read.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readDefaultLocale, readLocales } from "./locales.js";
|
|
17
|
+
|
|
18
|
+
export function resolveLocale(requested: string | undefined): string {
|
|
19
|
+
const defaultLocale = readDefaultLocale();
|
|
20
|
+
if (requested === undefined) return defaultLocale;
|
|
21
|
+
|
|
22
|
+
let allow: string[];
|
|
23
|
+
try {
|
|
24
|
+
allow = readLocales();
|
|
25
|
+
} catch {
|
|
26
|
+
allow = [defaultLocale];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return allow.includes(requested) ? requested : defaultLocale;
|
|
30
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server factory.
|
|
3
|
+
*
|
|
4
|
+
* Creates and configures the McpServer instance with all registered tools.
|
|
5
|
+
* The server is returned unconnected — callers attach a transport (stdio in
|
|
6
|
+
* production, in-memory in tests).
|
|
7
|
+
*
|
|
8
|
+
* Tool names + descriptions + input shapes come from the canonical catalog
|
|
9
|
+
* exported by `@working-theory/metadata` (issue #209). The catalog is the single source
|
|
10
|
+
* of truth shared with `apps/web /.well-known/mcp` for agent discovery.
|
|
11
|
+
* Handlers continue to live in `apps/mcp/src/tools/*` — this module wires
|
|
12
|
+
* each catalog entry to its handler.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { getMcpToolRegistrations, OSS_DISABLED_TOOLS } from "@working-theory/metadata";
|
|
16
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
|
+
|
|
18
|
+
import { ailkAudit } from "./tools/audit.js";
|
|
19
|
+
import { ailkCaptureLead } from "./tools/capture_lead.js";
|
|
20
|
+
import { ailkCreatePage } from "./tools/create_page.js";
|
|
21
|
+
import { ailkDeletePage } from "./tools/delete_page.js";
|
|
22
|
+
import { ailkGetBusinessHours } from "./tools/get_business_hours.js";
|
|
23
|
+
import { ailkGetFaq } from "./tools/get_faq.js";
|
|
24
|
+
import { ailkGetLocations } from "./tools/get_locations.js";
|
|
25
|
+
import { ailkGetOfferings } from "./tools/get_offerings.js";
|
|
26
|
+
import { ailkGetPricing } from "./tools/get_pricing.js";
|
|
27
|
+
import { ailkGetSiteInfo } from "./tools/get_site_info.js";
|
|
28
|
+
import { ailkListPages } from "./tools/list_pages.js";
|
|
29
|
+
import { ailkPurchase } from "./tools/purchase.js";
|
|
30
|
+
import { ailkReadContent } from "./tools/read_content.js";
|
|
31
|
+
import { ailkRequestCallback } from "./tools/request_callback.js";
|
|
32
|
+
import { ailkScaffold } from "./tools/scaffold.js";
|
|
33
|
+
import { ailkSearchContent } from "./tools/search_content.js";
|
|
34
|
+
import { ailkSubscribeNewsletter } from "./tools/subscribe_newsletter.js";
|
|
35
|
+
import { ailkUpdatePage } from "./tools/update_page.js";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tool-name → handler map. Each handler accepts the parsed input the SDK
|
|
39
|
+
* passes through after Zod validation against the catalog input shape.
|
|
40
|
+
*
|
|
41
|
+
* Cast via `never` is safe: each handler validates its input via its own
|
|
42
|
+
* Zod parse before doing real work (see apps/mcp/src/tools/*.ts).
|
|
43
|
+
*/
|
|
44
|
+
const HANDLERS: Record<string, (input: unknown) => Promise<unknown>> = {
|
|
45
|
+
start_purchase: (input) => ailkPurchase(input as never),
|
|
46
|
+
list_pages: (input) => ailkListPages(input as never),
|
|
47
|
+
read_content: (input) => ailkReadContent(input as never),
|
|
48
|
+
search_content: (input) => ailkSearchContent(input as never),
|
|
49
|
+
get_site_info: (input) => ailkGetSiteInfo(input as never),
|
|
50
|
+
get_business_hours: (input) => ailkGetBusinessHours(input as never),
|
|
51
|
+
get_locations: (input) => ailkGetLocations(input as never),
|
|
52
|
+
get_offerings: (input) => ailkGetOfferings(input as never),
|
|
53
|
+
get_pricing: (input) => ailkGetPricing(input as never),
|
|
54
|
+
get_faq: (input) => ailkGetFaq(input as never),
|
|
55
|
+
audit: (input) => {
|
|
56
|
+
const { slug, type, url } = input as {
|
|
57
|
+
slug?: string;
|
|
58
|
+
type?: string;
|
|
59
|
+
url?: string;
|
|
60
|
+
};
|
|
61
|
+
const handlerInput = url !== undefined ? { url } : { slug: slug!, type };
|
|
62
|
+
return ailkAudit(handlerInput as never);
|
|
63
|
+
},
|
|
64
|
+
capture_lead: (input) => ailkCaptureLead(input as never),
|
|
65
|
+
subscribe_newsletter: (input) => ailkSubscribeNewsletter(input as never),
|
|
66
|
+
request_callback: (input) => ailkRequestCallback(input as never),
|
|
67
|
+
scaffold: (input) => ailkScaffold(input as never),
|
|
68
|
+
// D14a Stage 1 Unit C (#55): Paid-tier content write surface. Handlers
|
|
69
|
+
// self-gate via requirePaidTier and return canonical error bodies (no throw)
|
|
70
|
+
// for license / validation / conflict / not-found cases — see
|
|
71
|
+
// apps/mcp/src/lib/errors.ts. Catalog entries carry tier: "pro" so agents
|
|
72
|
+
// see the gating before invoking via list_tools.
|
|
73
|
+
create_page: (input) => ailkCreatePage(input as never),
|
|
74
|
+
update_page: (input) => ailkUpdatePage(input as never),
|
|
75
|
+
delete_page: (input) => ailkDeletePage(input as never),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Tool descriptor returned by {@link listRegisteredTools}. A minimal shape
|
|
80
|
+
* sufficient for callers that need to assert which tools are registered
|
|
81
|
+
* (e.g., the S15a regression test) without depending on the SDK's internal
|
|
82
|
+
* `_registeredTools` field.
|
|
83
|
+
*/
|
|
84
|
+
export interface RegisteredToolDescriptor {
|
|
85
|
+
name: string;
|
|
86
|
+
description: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns the list of tools that {@link createMcpServer} will register, after
|
|
91
|
+
* the OSS catalog filter ({@link OSS_DISABLED_TOOLS}) is applied. Pure — no
|
|
92
|
+
* server instance is constructed.
|
|
93
|
+
*
|
|
94
|
+
* Consumed by `apps/mcp/__tests__/purchase-disabled.test.ts` to assert that
|
|
95
|
+
* disabled tools are absent from the registered catalog. If a future change
|
|
96
|
+
* removes a name from `OSS_DISABLED_TOOLS` without satisfying the Phase 3
|
|
97
|
+
* re-enable criteria, that test fails RED.
|
|
98
|
+
*/
|
|
99
|
+
export function listRegisteredTools(): RegisteredToolDescriptor[] {
|
|
100
|
+
return getMcpToolRegistrations()
|
|
101
|
+
.filter((reg) => !OSS_DISABLED_TOOLS.has(reg.name))
|
|
102
|
+
.map((reg) => ({ name: reg.name, description: reg.description }));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a configured McpServer with all AILK tools registered, less any
|
|
107
|
+
* tool name in {@link OSS_DISABLED_TOOLS}. The disabled filter is applied
|
|
108
|
+
* defensively here even though the upstream `getMcpToolRegistrations()`
|
|
109
|
+
* already excludes them — a belt-and-braces guard so a future regression in
|
|
110
|
+
* the metadata package can't accidentally re-register a disabled tool.
|
|
111
|
+
*
|
|
112
|
+
* Re-enable criteria for each disabled tool live at
|
|
113
|
+
* `docs/internal/roadmaps/phase-3-readiness.md`. S15a (issue #610) is the
|
|
114
|
+
* source of this filter.
|
|
115
|
+
*/
|
|
116
|
+
export function createMcpServer(): McpServer {
|
|
117
|
+
const server = new McpServer({
|
|
118
|
+
name: "ailk",
|
|
119
|
+
version: "0.1.0",
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
for (const reg of getMcpToolRegistrations()) {
|
|
123
|
+
if (OSS_DISABLED_TOOLS.has(reg.name)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const handler = HANDLERS[reg.name];
|
|
127
|
+
if (!handler) {
|
|
128
|
+
throw new Error(`MCP catalog drift: no handler for tool '${reg.name}'`);
|
|
129
|
+
}
|
|
130
|
+
server.tool(reg.name, reg.description, reg.inputShape, async (input) => {
|
|
131
|
+
const result = await handler(input);
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return server;
|
|
139
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* audit — runs the AEO Layer 1 scoring engine against a single page.
|
|
3
|
+
*
|
|
4
|
+
* Pure read: imports runAudit from @working-theory/aeo (ADR 0011). No auth required.
|
|
5
|
+
* The adapter (audit-single-page) resolves URL or slug to a filesystem read;
|
|
6
|
+
* no fetch() is called anywhere in this file or the adapter.
|
|
7
|
+
*
|
|
8
|
+
* Input: { slug, type? } | { url }
|
|
9
|
+
* slug branch — lowercase [a-z0-9-]+ slug; type optional (defaults BlogPosting)
|
|
10
|
+
* url branch — absolute URL on the AILK installation's own domain only
|
|
11
|
+
* (same-origin check uses AILK_BASE_URL || NEXT_PUBLIC_BASE_URL)
|
|
12
|
+
*
|
|
13
|
+
* Output: { score, perRule, perPage, meta }
|
|
14
|
+
* score — 0–100 overall
|
|
15
|
+
* perRule — per-rule breakdown (id, name, weight, status, evidence?, advice?)
|
|
16
|
+
* perPage — per-page summary (slug, type, score)
|
|
17
|
+
* meta — { auditedAt, ruleSet, locale }
|
|
18
|
+
*
|
|
19
|
+
* Session/scope: none — read-only public content.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
runAudit,
|
|
24
|
+
alwaysFails,
|
|
25
|
+
alwaysPasses,
|
|
26
|
+
entityMarkup,
|
|
27
|
+
faqHowtoIncentive,
|
|
28
|
+
jsonldValidity,
|
|
29
|
+
schemaCoverage,
|
|
30
|
+
type Rule,
|
|
31
|
+
} from "@working-theory/aeo";
|
|
32
|
+
import {
|
|
33
|
+
auditInputSchema,
|
|
34
|
+
auditOutputSchema,
|
|
35
|
+
type AuditInput,
|
|
36
|
+
type AuditOutput,
|
|
37
|
+
} from "@working-theory/validation";
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
buildSinglePageAuditInput,
|
|
41
|
+
type SinglePageInput,
|
|
42
|
+
} from "../lib/audit-single-page.js";
|
|
43
|
+
|
|
44
|
+
export { auditInputSchema, auditOutputSchema };
|
|
45
|
+
export type { AuditInput, AuditOutput };
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Canonical rule set — mirrors packages/aeo/src/cli/index.ts RULES array.
|
|
49
|
+
* Imported from @working-theory/aeo public API; must not be re-implemented here.
|
|
50
|
+
*/
|
|
51
|
+
const RULES: Rule[] = [
|
|
52
|
+
alwaysPasses,
|
|
53
|
+
alwaysFails,
|
|
54
|
+
schemaCoverage,
|
|
55
|
+
jsonldValidity,
|
|
56
|
+
faqHowtoIncentive,
|
|
57
|
+
entityMarkup,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* audit handler.
|
|
62
|
+
*
|
|
63
|
+
* Accepts an optional `contentRoot` override for testing. When omitted,
|
|
64
|
+
* resolves the content root relative to cwd (supports ESM runtime).
|
|
65
|
+
*/
|
|
66
|
+
export async function ailkAudit(
|
|
67
|
+
input: AuditInput,
|
|
68
|
+
contentRoot?: string,
|
|
69
|
+
): Promise<AuditOutput> {
|
|
70
|
+
auditInputSchema.parse(input);
|
|
71
|
+
|
|
72
|
+
// Cast: AuditInput's Zod-inferred optional fields include `undefined` in
|
|
73
|
+
// their union, but SinglePageInput uses exactOptionalPropertyTypes where `?`
|
|
74
|
+
// means "absent." The auditInputSchema.parse above guarantees validity.
|
|
75
|
+
const auditInput = await buildSinglePageAuditInput(
|
|
76
|
+
input as unknown as SinglePageInput,
|
|
77
|
+
contentRoot,
|
|
78
|
+
);
|
|
79
|
+
const report = runAudit(auditInput, RULES);
|
|
80
|
+
|
|
81
|
+
const result: AuditOutput = {
|
|
82
|
+
score: report.score,
|
|
83
|
+
perRule: report.perRule.map((r) => ({
|
|
84
|
+
ruleId: r.id,
|
|
85
|
+
name: r.id, // rule description not on PerRuleResult; use id as name
|
|
86
|
+
weight: r.weight,
|
|
87
|
+
status: r.pass ? "pass" : "fail",
|
|
88
|
+
...(r.evidence !== undefined && { evidence: r.evidence }),
|
|
89
|
+
})),
|
|
90
|
+
perPage: report.perPage.map((p) => ({
|
|
91
|
+
slug: p.slug,
|
|
92
|
+
type: p.type,
|
|
93
|
+
score: p.score,
|
|
94
|
+
})),
|
|
95
|
+
meta: {
|
|
96
|
+
auditedAt: report.meta.auditedAt,
|
|
97
|
+
ruleSet: `${report.meta.schemaVersion}/${report.meta.registryVersion}`,
|
|
98
|
+
locale: auditInput.locale,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return auditOutputSchema.parse(result);
|
|
103
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capture_lead — capture a contact-form lead.
|
|
3
|
+
*
|
|
4
|
+
* Validates input via Zod, forwards to POST /api/leads/submit via
|
|
5
|
+
* @working-theory/api-client, and shapes the response into the MCP output schema.
|
|
6
|
+
* No business logic — orchestration lives in apps/api B6.
|
|
7
|
+
*
|
|
8
|
+
* Paired with captureLeadAction Server Action per ADR 0013.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { apiClient } from "@working-theory/api-client";
|
|
12
|
+
import {
|
|
13
|
+
submitLeadInputSchema,
|
|
14
|
+
submitLeadOutputSchema,
|
|
15
|
+
type SubmitLeadInput,
|
|
16
|
+
type SubmitLeadOutput,
|
|
17
|
+
} from "@working-theory/validation";
|
|
18
|
+
|
|
19
|
+
export { submitLeadInputSchema, submitLeadOutputSchema };
|
|
20
|
+
export type { SubmitLeadInput, SubmitLeadOutput };
|
|
21
|
+
|
|
22
|
+
export async function ailkCaptureLead(
|
|
23
|
+
input: SubmitLeadInput,
|
|
24
|
+
): Promise<SubmitLeadOutput> {
|
|
25
|
+
const parsed = submitLeadInputSchema.safeParse(input);
|
|
26
|
+
if (!parsed.success) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
error: { kind: "invalid_input", issues: parsed.error.issues },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { name, email, message, phone } = parsed.data;
|
|
34
|
+
const siteId = process.env.AILK_SITE_ID ?? "default";
|
|
35
|
+
const body = {
|
|
36
|
+
siteId,
|
|
37
|
+
source: "mcp",
|
|
38
|
+
email,
|
|
39
|
+
payload: {
|
|
40
|
+
name,
|
|
41
|
+
...(message !== undefined && { message }),
|
|
42
|
+
...(phone !== undefined && { phone }),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const res = await apiClient.leads.submit(body);
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
return { ok: false, error: { kind: "api_error", status: res.status } };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { ok: true, id: res.body.id, persisted: res.body.persisted };
|
|
52
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* create_page — Paid-tier MCP tool for authoring a new content page.
|
|
3
|
+
*
|
|
4
|
+
* D14a Stage 1 Unit C — wires the McpServer create_page handler to the
|
|
5
|
+
* ContentAdapter.createPage method (Stage 1 Unit A implements the MDX
|
|
6
|
+
* adapter; tests inject a fake adapter).
|
|
7
|
+
*
|
|
8
|
+
* Surface contract: validates input via FrontmatterSchema.extend({ body }),
|
|
9
|
+
* runs the Paid-tier license guard, calls adapter.createPage, and maps adapter
|
|
10
|
+
* errors to canonical MCP error bodies (see apps/mcp/src/lib/errors.ts).
|
|
11
|
+
*
|
|
12
|
+
* Input shape mirrors the Stage 0 catalog entry in
|
|
13
|
+
* packages/metadata/src/mcp-tool-catalog.ts. Catalog parses a permissive
|
|
14
|
+
* outer shape (`page: record`); this handler parses strictly via
|
|
15
|
+
* FrontmatterSchema so bad frontmatter surfaces as a validation_failed body.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
FrontmatterSchema,
|
|
22
|
+
createMdxAdapter,
|
|
23
|
+
mapAdapterError,
|
|
24
|
+
type ContentAdapter,
|
|
25
|
+
} from "@working-theory/content-adapters";
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
|
|
28
|
+
import { validationError, type ErrorBody } from "../lib/errors.js";
|
|
29
|
+
import { requirePaidTier } from "../lib/require-paid-tier.js";
|
|
30
|
+
|
|
31
|
+
const CreatePageInputSchema = FrontmatterSchema.extend({
|
|
32
|
+
body: z.string().min(1),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
interface CreatePageSuccess {
|
|
36
|
+
page: Record<string, unknown>;
|
|
37
|
+
version: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type CreatePageResult = CreatePageSuccess | ErrorBody;
|
|
41
|
+
|
|
42
|
+
export interface CreatePageInput {
|
|
43
|
+
type: string;
|
|
44
|
+
slug: string;
|
|
45
|
+
page: Record<string, unknown>;
|
|
46
|
+
body: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* ailkCreatePage handler.
|
|
51
|
+
*
|
|
52
|
+
* Optional `adapter` injection mirrors the Q3 resolution in the resource plan
|
|
53
|
+
* (tests construct adapters directly with isolated content roots). When
|
|
54
|
+
* omitted, falls back to the default MDX adapter rooted at process.cwd()/content.
|
|
55
|
+
*/
|
|
56
|
+
export async function ailkCreatePage(
|
|
57
|
+
input: CreatePageInput,
|
|
58
|
+
adapter?: ContentAdapter,
|
|
59
|
+
): Promise<CreatePageResult> {
|
|
60
|
+
const guard = await requirePaidTier();
|
|
61
|
+
if (guard) return guard;
|
|
62
|
+
|
|
63
|
+
const parsed = CreatePageInputSchema.safeParse({
|
|
64
|
+
...input.page,
|
|
65
|
+
type: input.type,
|
|
66
|
+
slug: input.slug,
|
|
67
|
+
body: input.body,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!parsed.success) {
|
|
71
|
+
return validationError("Invalid frontmatter or body for create_page.", {
|
|
72
|
+
issues: parsed.error.issues,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const resolved =
|
|
77
|
+
adapter ??
|
|
78
|
+
createMdxAdapter({ contentRoot: join(process.cwd(), "content") });
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const result = await resolved.createPage(
|
|
82
|
+
parsed.data.type,
|
|
83
|
+
parsed.data.slug,
|
|
84
|
+
parsed.data,
|
|
85
|
+
);
|
|
86
|
+
return { page: result.page, version: result.version };
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const mapped = mapAdapterError(err, {
|
|
89
|
+
type: parsed.data.type,
|
|
90
|
+
slug: parsed.data.slug,
|
|
91
|
+
});
|
|
92
|
+
if (mapped) return mapped;
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
}
|