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,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file factories/index.ts
|
|
3
|
+
* Typed test data factories for apps/api route tests.
|
|
4
|
+
*
|
|
5
|
+
* Each factory returns a fully-typed object with all required fields populated.
|
|
6
|
+
* Pass overrides to vary only the fields relevant to the test case.
|
|
7
|
+
*
|
|
8
|
+
* Used by: content, leads, checkout route tests (Issue #507 real-dependency refactor).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ContentPage } from "@working-theory/content-adapters";
|
|
12
|
+
import type { Lead, LeadCaptureRecord } from "@working-theory/validation";
|
|
13
|
+
|
|
14
|
+
// ─── Content factories ────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export type ContentPageOverrides = Partial<ContentPage>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build a fully-typed ContentPage. Defaults to a HomePage with deterministic values.
|
|
20
|
+
*/
|
|
21
|
+
export function buildContent(
|
|
22
|
+
overrides: ContentPageOverrides = {},
|
|
23
|
+
): ContentPage {
|
|
24
|
+
return {
|
|
25
|
+
type: "HomePage",
|
|
26
|
+
slug: "test-page",
|
|
27
|
+
title: "Test Page Title",
|
|
28
|
+
description: "A test page description for automated tests.",
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Lead factories ───────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
export type LeadOverrides = Partial<Lead>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a fully-typed Lead record (as returned by the DB / written to file).
|
|
39
|
+
*/
|
|
40
|
+
export function buildLead(overrides: LeadOverrides = {}): Lead {
|
|
41
|
+
return {
|
|
42
|
+
id: "test-lead-id-00000001",
|
|
43
|
+
siteId: "test-site-abc",
|
|
44
|
+
source: "contact",
|
|
45
|
+
email: "test@example.com",
|
|
46
|
+
payload: { message: "Hello from factory" },
|
|
47
|
+
createdAt: new Date().toISOString(),
|
|
48
|
+
...overrides,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── LeadCaptureRecord factories ──────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
export type LeadCaptureRecordOverrides = Partial<LeadCaptureRecord>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build a fully-typed LeadCaptureRecord (as written to the JSONL capture file).
|
|
58
|
+
*/
|
|
59
|
+
export function buildLeadCaptureRecord(
|
|
60
|
+
overrides: LeadCaptureRecordOverrides = {},
|
|
61
|
+
): LeadCaptureRecord {
|
|
62
|
+
return {
|
|
63
|
+
id: "test-capture-id-00000001",
|
|
64
|
+
createdAt: new Date().toISOString(),
|
|
65
|
+
source: "contact",
|
|
66
|
+
data: { email: "test@example.com" },
|
|
67
|
+
metadata: {},
|
|
68
|
+
...overrides,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lead-fixture.ts
|
|
3
|
+
* Filesystem fixture helpers for leads route tests.
|
|
4
|
+
*
|
|
5
|
+
* Sets up per-test temp paths for the real lead-capture-store and
|
|
6
|
+
* file-fallback modules, so tests can assert on actual file writes
|
|
7
|
+
* without mocking those internal modules.
|
|
8
|
+
*
|
|
9
|
+
* Issue #507: replaces jest.mock("../../../lib/lead-capture-store.js") and
|
|
10
|
+
* jest.mock("../../../lib/file-fallback.js") in leads route tests.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from "fs/promises";
|
|
14
|
+
import * as os from "os";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
|
|
17
|
+
export interface LeadFileFixture {
|
|
18
|
+
/** Path to the per-test JSONL capture file (LEAD_CAPTURE_FILE). */
|
|
19
|
+
getCaptureFilePath: () => string;
|
|
20
|
+
/** Path to the per-test fallback dir (AILK_INBOX_FALLBACK_DIR). */
|
|
21
|
+
getFallbackDir: () => string;
|
|
22
|
+
/**
|
|
23
|
+
* Read and parse the last line written to the capture file.
|
|
24
|
+
* Throws if the file does not exist or is empty.
|
|
25
|
+
*/
|
|
26
|
+
readLastCaptureRecord: () => Promise<Record<string, unknown>>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Register beforeEach/afterEach hooks that create a fresh LEAD_CAPTURE_FILE
|
|
31
|
+
* path and AILK_INBOX_FALLBACK_DIR per test, then clean up afterward.
|
|
32
|
+
*
|
|
33
|
+
* Call at describe-scope. Returns a handle for asserting on file contents.
|
|
34
|
+
*/
|
|
35
|
+
export function setupLeadFileEnv(label = "leads"): LeadFileFixture {
|
|
36
|
+
let captureFilePath = "";
|
|
37
|
+
let fallbackDir = "";
|
|
38
|
+
|
|
39
|
+
beforeEach(async () => {
|
|
40
|
+
captureFilePath = path.join(
|
|
41
|
+
os.tmpdir(),
|
|
42
|
+
`ailk-test-${label}-${Date.now()}.jsonl`,
|
|
43
|
+
);
|
|
44
|
+
process.env.LEAD_CAPTURE_FILE = captureFilePath;
|
|
45
|
+
|
|
46
|
+
fallbackDir = await fs.mkdtemp(
|
|
47
|
+
path.join(os.tmpdir(), `ailk-test-fallback-${label}-`),
|
|
48
|
+
);
|
|
49
|
+
process.env.AILK_INBOX_FALLBACK_DIR = fallbackDir;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(async () => {
|
|
53
|
+
delete process.env.LEAD_CAPTURE_FILE;
|
|
54
|
+
delete process.env.AILK_INBOX_FALLBACK_DIR;
|
|
55
|
+
|
|
56
|
+
// Best-effort cleanup — never fail the test on cleanup errors.
|
|
57
|
+
try {
|
|
58
|
+
await fs.unlink(captureFilePath);
|
|
59
|
+
} catch {
|
|
60
|
+
// file may not exist if appendLeadRecord was skipped
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
await fs.rm(fallbackDir, { recursive: true, force: true });
|
|
64
|
+
} catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
captureFilePath = "";
|
|
69
|
+
fallbackDir = "";
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
getCaptureFilePath: () => captureFilePath,
|
|
74
|
+
getFallbackDir: () => fallbackDir,
|
|
75
|
+
readLastCaptureRecord: async () => {
|
|
76
|
+
const content = await fs.readFile(captureFilePath, "utf8");
|
|
77
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
78
|
+
if (lines.length === 0) throw new Error("Capture file is empty");
|
|
79
|
+
return JSON.parse(lines[lines.length - 1]) as Record<string, unknown>;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.test.ts
|
|
3
|
+
* @description Unit tests for index.ts - Entry point
|
|
4
|
+
*
|
|
5
|
+
* Uses jest.spyOn on the real build() function (from ../server) to stub the
|
|
6
|
+
* server object returned, rather than mocking the server module wholesale.
|
|
7
|
+
* The real config module is imported directly — PORT and NODE_ENV come from
|
|
8
|
+
* the test setup env vars.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { start } from '../index';
|
|
12
|
+
import * as serverModule from '../server';
|
|
13
|
+
|
|
14
|
+
// Mock @working-theory/observability so assertions check structured logger calls
|
|
15
|
+
// rather than depending on pino's stdout serialization.
|
|
16
|
+
const mockLoggerInfo = jest.fn();
|
|
17
|
+
const mockLoggerError = jest.fn();
|
|
18
|
+
jest.mock('@working-theory/observability', () => ({
|
|
19
|
+
logger: {
|
|
20
|
+
info: (...args: unknown[]) => mockLoggerInfo(...args),
|
|
21
|
+
error: (...args: unknown[]) => mockLoggerError(...args),
|
|
22
|
+
warn: jest.fn(),
|
|
23
|
+
debug: jest.fn(),
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// @working-theory/email — ESM-only transitive dep (@react-email/components) cannot be
|
|
28
|
+
// resolved by jest-resolve in CommonJS mode. Mock at the package boundary.
|
|
29
|
+
jest.mock('@working-theory/email', () => ({
|
|
30
|
+
sendLeadNotification: jest.fn().mockResolvedValue({ ok: true }),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// External Fastify plugins — mocked because they require live infrastructure.
|
|
34
|
+
jest.mock('@fastify/cors', () => jest.fn().mockResolvedValue(undefined));
|
|
35
|
+
jest.mock('@fastify/rate-limit', () => jest.fn().mockResolvedValue(undefined));
|
|
36
|
+
|
|
37
|
+
describe('Index Entry Point', () => {
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mockServer holds the Jest-stubbed Fastify instance; typed loosely to avoid importing full FastifyInstance here
|
|
39
|
+
let mockServer: any;
|
|
40
|
+
let buildSpy: jest.SpyInstance;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
jest.clearAllMocks();
|
|
44
|
+
mockLoggerInfo.mockClear();
|
|
45
|
+
mockLoggerError.mockClear();
|
|
46
|
+
|
|
47
|
+
// Re-establish spies each test so afterEach's restoreAllMocks doesn't leave
|
|
48
|
+
// process.exit unmocked across tests — the real exit would kill the worker.
|
|
49
|
+
jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
50
|
+
throw new Error('Process exit called');
|
|
51
|
+
}) as never);
|
|
52
|
+
|
|
53
|
+
mockServer = {
|
|
54
|
+
listen: jest.fn().mockResolvedValue(undefined),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Spy on build() from the real server module instead of mocking the module.
|
|
58
|
+
// This keeps the module graph intact while controlling the returned instance.
|
|
59
|
+
buildSpy = jest.spyOn(serverModule, 'build').mockResolvedValue(mockServer);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
jest.restoreAllMocks();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('start function', () => {
|
|
67
|
+
it('should build server and start listening on configured port', async () => {
|
|
68
|
+
// ACT
|
|
69
|
+
await start();
|
|
70
|
+
|
|
71
|
+
// ASSERT
|
|
72
|
+
expect(buildSpy).toHaveBeenCalled();
|
|
73
|
+
expect(mockServer.listen).toHaveBeenCalledWith({
|
|
74
|
+
port: 3002,
|
|
75
|
+
host: '0.0.0.0',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should log server startup information', async () => {
|
|
80
|
+
// ACT
|
|
81
|
+
await start();
|
|
82
|
+
|
|
83
|
+
// ASSERT — single structured info call covers port + env + endpoints.
|
|
84
|
+
expect(mockLoggerInfo).toHaveBeenCalledTimes(1);
|
|
85
|
+
const [payload, msg] = mockLoggerInfo.mock.calls[0];
|
|
86
|
+
expect(msg).toContain('AI Launch Kit API');
|
|
87
|
+
expect(payload).toMatchObject({
|
|
88
|
+
port: 3002,
|
|
89
|
+
env: 'test',
|
|
90
|
+
endpoints: expect.objectContaining({ auth: expect.stringContaining('/v1/auth/tokens') }),
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle server build failure', async () => {
|
|
95
|
+
// ARRANGE
|
|
96
|
+
const buildError = new Error('Build failed');
|
|
97
|
+
buildSpy.mockRejectedValue(buildError);
|
|
98
|
+
|
|
99
|
+
// ACT & ASSERT - process.exit throws due to mock
|
|
100
|
+
await expect(start()).rejects.toThrow('Process exit called');
|
|
101
|
+
const [payload, msg] = mockLoggerError.mock.calls[0];
|
|
102
|
+
expect(msg).toBe('Failed to start server');
|
|
103
|
+
expect(payload).toMatchObject({ err: expect.any(Error) });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle server listen failure', async () => {
|
|
107
|
+
// ARRANGE
|
|
108
|
+
const listenError = new Error('Port already in use');
|
|
109
|
+
mockServer.listen.mockRejectedValue(listenError);
|
|
110
|
+
|
|
111
|
+
// ACT & ASSERT - process.exit throws due to mock
|
|
112
|
+
await expect(start()).rejects.toThrow('Process exit called');
|
|
113
|
+
const [payload, msg] = mockLoggerError.mock.calls[0];
|
|
114
|
+
expect(msg).toBe('Failed to start server');
|
|
115
|
+
expect(payload).toMatchObject({ err: expect.any(Error) });
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file server.test.ts
|
|
3
|
+
* @description Unit tests for server.ts - Server setup
|
|
4
|
+
*
|
|
5
|
+
* Uses the real build() function with real route plugins. All internal modules
|
|
6
|
+
* run real; external boundaries (CORS, rate-limit) are mocked at the plugin
|
|
7
|
+
* boundary since they would require live network/Redis in production.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { build } from '../server';
|
|
11
|
+
|
|
12
|
+
// External service boundaries — mocked because they require live infrastructure
|
|
13
|
+
// or have ESM-only transitive deps that Jest's CJS preset cannot resolve:
|
|
14
|
+
// @fastify/cors, @fastify/rate-limit — Fastify plugins that would require
|
|
15
|
+
// live network infrastructure (CORS policy sync, Redis store) in unit tests.
|
|
16
|
+
// @working-theory/email — transitive dep on @react-email/components which is ESM-only
|
|
17
|
+
// and cannot be resolved by jest-resolve in CommonJS mode.
|
|
18
|
+
jest.mock('@fastify/cors', () => {
|
|
19
|
+
return jest.fn().mockResolvedValue(undefined);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
jest.mock('@fastify/rate-limit', () => {
|
|
23
|
+
return jest.fn().mockResolvedValue(undefined);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
jest.mock('@working-theory/email', () => ({
|
|
27
|
+
sendLeadNotification: jest.fn().mockResolvedValue({ ok: true }),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
describe('Server', () => {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- server is typed via Fastify generics; explicit any avoids pulling in the full FastifyInstance type here
|
|
32
|
+
let app: any;
|
|
33
|
+
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(async () => {
|
|
39
|
+
if (app) {
|
|
40
|
+
await app.close();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('build function', () => {
|
|
45
|
+
it('should build Fastify server instance', async () => {
|
|
46
|
+
// ACT
|
|
47
|
+
app = await build();
|
|
48
|
+
|
|
49
|
+
// ASSERT
|
|
50
|
+
expect(app).toBeDefined();
|
|
51
|
+
expect(typeof app.inject).toBe('function');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should register health check route returning static OK without external calls', async () => {
|
|
55
|
+
// ARRANGE
|
|
56
|
+
app = await build();
|
|
57
|
+
|
|
58
|
+
// ACT
|
|
59
|
+
const response = await app.inject({
|
|
60
|
+
method: 'GET',
|
|
61
|
+
url: '/health',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ASSERT
|
|
65
|
+
expect(response.statusCode).toBe(200);
|
|
66
|
+
const body = JSON.parse(response.body);
|
|
67
|
+
expect(body.status).toBe('healthy');
|
|
68
|
+
expect(body.timestamp).toBeDefined();
|
|
69
|
+
// No external dependencies in the health check anymore
|
|
70
|
+
expect(body.platform).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should register root endpoint', async () => {
|
|
74
|
+
// ARRANGE
|
|
75
|
+
app = await build();
|
|
76
|
+
|
|
77
|
+
// ACT
|
|
78
|
+
const response = await app.inject({
|
|
79
|
+
method: 'GET',
|
|
80
|
+
url: '/',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ASSERT
|
|
84
|
+
expect(response.statusCode).toBe(200);
|
|
85
|
+
const body = JSON.parse(response.body);
|
|
86
|
+
expect(body.name).toBe('AI Launch Kit API');
|
|
87
|
+
expect(body.endpoints).toBeDefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should register CORS', async () => {
|
|
91
|
+
// ACT
|
|
92
|
+
app = await build();
|
|
93
|
+
|
|
94
|
+
// ASSERT - CORS should be registered (no error thrown)
|
|
95
|
+
expect(app).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should register all route plugins', async () => {
|
|
99
|
+
// ARRANGE
|
|
100
|
+
app = await build();
|
|
101
|
+
|
|
102
|
+
// ACT & ASSERT - Test that routes are registered by checking they don't 404
|
|
103
|
+
const healthResponse = await app.inject({
|
|
104
|
+
method: 'GET',
|
|
105
|
+
url: '/health',
|
|
106
|
+
});
|
|
107
|
+
expect(healthResponse.statusCode).toBe(200);
|
|
108
|
+
|
|
109
|
+
const rootResponse = await app.inject({
|
|
110
|
+
method: 'GET',
|
|
111
|
+
url: '/',
|
|
112
|
+
});
|
|
113
|
+
expect(rootResponse.statusCode).toBe(200);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should include all endpoints in root response', async () => {
|
|
117
|
+
// ARRANGE
|
|
118
|
+
app = await build();
|
|
119
|
+
|
|
120
|
+
// ACT
|
|
121
|
+
const response = await app.inject({
|
|
122
|
+
method: 'GET',
|
|
123
|
+
url: '/',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ASSERT
|
|
127
|
+
const body = JSON.parse(response.body);
|
|
128
|
+
expect(body.endpoints).toHaveProperty('health');
|
|
129
|
+
expect(body.endpoints).toHaveProperty('tokens');
|
|
130
|
+
expect(body.endpoints).toHaveProperty('profile');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('startup throws when LEAD_CAPTURE_FILE points at a missing directory', async () => {
|
|
134
|
+
// ARRANGE — set LEAD_CAPTURE_FILE to a path whose parent dir doesn't exist
|
|
135
|
+
// so the real validateLeadCaptureDir() throws (AC8). Restore after test.
|
|
136
|
+
const prior = process.env.LEAD_CAPTURE_FILE;
|
|
137
|
+
process.env.LEAD_CAPTURE_FILE = '/nonexistent-ailk-test-dir/leads.jsonl';
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// ACT & ASSERT
|
|
141
|
+
await expect(build()).rejects.toThrow(/LEAD_CAPTURE_FILE/);
|
|
142
|
+
} finally {
|
|
143
|
+
if (prior === undefined) {
|
|
144
|
+
delete process.env.LEAD_CAPTURE_FILE;
|
|
145
|
+
} else {
|
|
146
|
+
process.env.LEAD_CAPTURE_FILE = prior;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ── Bootstrap test: raw-body parser scope encapsulation (AC 13) ──────────────
|
|
154
|
+
//
|
|
155
|
+
// Proves that registering the webhooks plugin (with its raw-body parser scoped
|
|
156
|
+
// to application/json) does NOT leak that parser to other routes. The global
|
|
157
|
+
// Fastify JSON parser must remain active for all non-webhook routes.
|
|
158
|
+
|
|
159
|
+
import Fastify from 'fastify';
|
|
160
|
+
import { webhooksRoutes } from '../routes/webhooks/index.js';
|
|
161
|
+
|
|
162
|
+
describe('Parser scope — webhooks raw-body parser does not leak', () => {
|
|
163
|
+
it('other routes still receive parsed JSON objects after webhooks plugin registers', async () => {
|
|
164
|
+
// Build a minimal server that mirrors the encapsulation pattern in server.ts:
|
|
165
|
+
// 1. Register a JSON route that echoes the parsed body.
|
|
166
|
+
// 2. Register the webhooks plugin inside an encapsulated scope with a
|
|
167
|
+
// raw-body (buffer) parser.
|
|
168
|
+
// If the raw-body parser leaks out of the webhooks scope, the echo route
|
|
169
|
+
// would receive a Buffer instead of a parsed object.
|
|
170
|
+
|
|
171
|
+
const server = Fastify({ logger: false });
|
|
172
|
+
|
|
173
|
+
// A simple JSON echo route — simulates any existing JSON-parsed route
|
|
174
|
+
server.post('/api/echo', async (req) => ({ received: req.body }));
|
|
175
|
+
|
|
176
|
+
// Mirror of server.ts registration pattern
|
|
177
|
+
await server.register(async (webhooksScope) => {
|
|
178
|
+
webhooksScope.addContentTypeParser(
|
|
179
|
+
'application/json',
|
|
180
|
+
{ parseAs: 'buffer' },
|
|
181
|
+
(_req, body, done) => {
|
|
182
|
+
done(null, body);
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
await webhooksScope.register(webhooksRoutes);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await server.ready();
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const response = await server.inject({
|
|
192
|
+
method: 'POST',
|
|
193
|
+
url: '/api/echo',
|
|
194
|
+
payload: { hello: 'world' },
|
|
195
|
+
headers: { 'content-type': 'application/json' },
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(response.statusCode).toBe(200);
|
|
199
|
+
const body = JSON.parse(response.body);
|
|
200
|
+
// Must be a plain object, not a stringified Buffer
|
|
201
|
+
expect(body.received).toEqual({ hello: 'world' });
|
|
202
|
+
expect(Buffer.isBuffer(body.received)).toBe(false);
|
|
203
|
+
} finally {
|
|
204
|
+
await server.close();
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file enrichment-shim.test.ts
|
|
3
|
+
* @description Unit tests for enrichment-shim — license-gated lead enrichment stub.
|
|
4
|
+
*
|
|
5
|
+
* Module-state hygiene: proSentinelLogged is module-scoped and persists across calls
|
|
6
|
+
* within the same module instance. Each test uses jest.isolateModules() to load a
|
|
7
|
+
* fresh copy of the shim (resetting proSentinelLogged) while keeping the jest.mock
|
|
8
|
+
* factory active so @working-theory/license is always intercepted.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createHash } from "crypto";
|
|
12
|
+
|
|
13
|
+
// Stable mock function reference — survives resetModules because it lives in this
|
|
14
|
+
// module's scope, not in the module registry.
|
|
15
|
+
const mockCheckLicense = jest.fn();
|
|
16
|
+
const mockLoggerInfo = jest.fn();
|
|
17
|
+
|
|
18
|
+
jest.mock("@working-theory/license", () => ({
|
|
19
|
+
checkLicense: (...args: unknown[]) => mockCheckLicense(...args),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock("@working-theory/observability", () => ({
|
|
23
|
+
logger: {
|
|
24
|
+
info: (...args: unknown[]) => mockLoggerInfo(...args),
|
|
25
|
+
warn: jest.fn(),
|
|
26
|
+
error: jest.fn(),
|
|
27
|
+
debug: jest.fn(),
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
describe("enrichLead", () => {
|
|
32
|
+
let enrichLead: (lead: unknown) => Promise<unknown>;
|
|
33
|
+
const consoleInfoSpy = mockLoggerInfo;
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
mockCheckLicense.mockReset();
|
|
37
|
+
mockLoggerInfo.mockClear();
|
|
38
|
+
|
|
39
|
+
// Load a fresh shim module instance per test to reset proSentinelLogged.
|
|
40
|
+
jest.isolateModules(() => {
|
|
41
|
+
|
|
42
|
+
const shim = require("../../services/enrichment-shim.js") as {
|
|
43
|
+
enrichLead: (lead: unknown) => Promise<unknown>;
|
|
44
|
+
};
|
|
45
|
+
enrichLead = shim.enrichLead;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
jest.restoreAllMocks();
|
|
51
|
+
delete process.env["AILK_LICENSE_KEY"];
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("free tier — returns lead unchanged, no sentinel log", async () => {
|
|
55
|
+
mockCheckLicense.mockResolvedValue({
|
|
56
|
+
tier: "free",
|
|
57
|
+
plan: null,
|
|
58
|
+
features: [],
|
|
59
|
+
expiresAt: null,
|
|
60
|
+
revoked: false,
|
|
61
|
+
});
|
|
62
|
+
const lead = { email: "test@example.com", name: "Test User" };
|
|
63
|
+
|
|
64
|
+
const result = await enrichLead(lead);
|
|
65
|
+
|
|
66
|
+
expect(result).toBe(lead);
|
|
67
|
+
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("pro tier (first call) — returns lead unchanged, sentinel logged once with key hash prefix", async () => {
|
|
71
|
+
mockCheckLicense.mockResolvedValue({
|
|
72
|
+
tier: "pro",
|
|
73
|
+
plan: "pro",
|
|
74
|
+
features: [],
|
|
75
|
+
expiresAt: null,
|
|
76
|
+
revoked: false,
|
|
77
|
+
});
|
|
78
|
+
process.env["AILK_LICENSE_KEY"] = "test-key-123";
|
|
79
|
+
|
|
80
|
+
const expectedPrefix = createHash("sha256")
|
|
81
|
+
.update("test-key-123")
|
|
82
|
+
.digest("hex")
|
|
83
|
+
.substring(0, 8);
|
|
84
|
+
|
|
85
|
+
const lead = { email: "test@example.com", name: "Test User" };
|
|
86
|
+
|
|
87
|
+
const result = await enrichLead(lead);
|
|
88
|
+
|
|
89
|
+
expect(result).toBe(lead);
|
|
90
|
+
expect(consoleInfoSpy).toHaveBeenCalledTimes(1);
|
|
91
|
+
// pino convention: logger.info(structuredPayload, message).
|
|
92
|
+
const [payload] = consoleInfoSpy.mock.calls[0];
|
|
93
|
+
expect(payload).toMatchObject({ licensePrefix: expectedPrefix });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("pro tier (second call, same module) — sentinel logged only once total", async () => {
|
|
97
|
+
mockCheckLicense.mockResolvedValue({
|
|
98
|
+
tier: "pro",
|
|
99
|
+
plan: "pro",
|
|
100
|
+
features: [],
|
|
101
|
+
expiresAt: null,
|
|
102
|
+
revoked: false,
|
|
103
|
+
});
|
|
104
|
+
process.env["AILK_LICENSE_KEY"] = "test-key-123";
|
|
105
|
+
|
|
106
|
+
const lead1 = { email: "first@example.com", name: "First" };
|
|
107
|
+
const lead2 = { email: "second@example.com", name: "Second" };
|
|
108
|
+
|
|
109
|
+
const result1 = await enrichLead(lead1);
|
|
110
|
+
const result2 = await enrichLead(lead2);
|
|
111
|
+
|
|
112
|
+
expect(result1).toBe(lead1);
|
|
113
|
+
expect(result2).toBe(lead2);
|
|
114
|
+
expect(consoleInfoSpy).toHaveBeenCalledTimes(1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("checkLicense throws — returns lead unchanged, no sentinel, error not propagated", async () => {
|
|
118
|
+
mockCheckLicense.mockRejectedValue(new Error("network error"));
|
|
119
|
+
const lead = { email: "test@example.com", name: "Test User" };
|
|
120
|
+
|
|
121
|
+
const result = await enrichLead(lead);
|
|
122
|
+
|
|
123
|
+
expect(result).toBe(lead);
|
|
124
|
+
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Global Setup
|
|
3
|
+
* Sets required environment variables for all tests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Database
|
|
7
|
+
process.env.DATABASE_URL = process.env.DATABASE_URL || 'postgresql://test:test@localhost:5432/test';
|
|
8
|
+
process.env.DATABASE_URL_NON_POOLED = process.env.DATABASE_URL_NON_POOLED || process.env.DATABASE_URL;
|
|
9
|
+
|
|
10
|
+
// Auth — Better Auth (post-#587). NEXTAUTH_* removed.
|
|
11
|
+
process.env.BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET || 'test-secret-key';
|
|
12
|
+
process.env.JWT_SECRET = process.env.JWT_SECRET || 'test-jwt-secret-min-32-characters-required!!';
|
|
13
|
+
process.env.GOOGLE_OAUTH_CLIENT_ID = process.env.GOOGLE_OAUTH_CLIENT_ID || 'test-google-client-id.apps.googleusercontent.com';
|
|
14
|
+
|
|
15
|
+
// Stripe
|
|
16
|
+
process.env.STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY || 'sk_test_mock_key';
|
|
17
|
+
process.env.STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET || 'whsec_test_webhook_secret';
|
|
18
|
+
process.env.STRIPE_API_VERSION = process.env.STRIPE_API_VERSION || '2023-10-16';
|
|
19
|
+
|
|
20
|
+
// Frontend
|
|
21
|
+
process.env.FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:3001';
|
|
22
|
+
|
|
23
|
+
// Node environment
|
|
24
|
+
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* istanbul ignore file -- Node CLI bootstrap; import.meta.url is unreachable from Jest CJS.
|
|
3
|
+
See the JSDoc below and docs at https://jestjs.io/docs/configuration#coverageprovider-string.
|
|
4
|
+
Real entry-point semantics are integration-tested by running the compiled dist directly. */
|
|
5
|
+
/**
|
|
6
|
+
* CLI Entry Point
|
|
7
|
+
*
|
|
8
|
+
* This file is the actual entry point for running the server from CLI.
|
|
9
|
+
* Separated from index.ts to allow Jest testing without parsing import.meta.
|
|
10
|
+
*/
|
|
11
|
+
import { pathToFileURL } from 'url';
|
|
12
|
+
|
|
13
|
+
import { start } from './index.js';
|
|
14
|
+
|
|
15
|
+
// Only start if this is the main module
|
|
16
|
+
// Check if executed file matches current file using import.meta.url
|
|
17
|
+
if (process.argv[1]) {
|
|
18
|
+
const isEntryPoint = process.argv[1].endsWith('dist/cli.js') ||
|
|
19
|
+
process.argv[1].endsWith('cli.js');
|
|
20
|
+
|
|
21
|
+
if (isEntryPoint) {
|
|
22
|
+
// ESM-accurate verification using import.meta.url
|
|
23
|
+
const currentFileUrl = import.meta.url;
|
|
24
|
+
const executedFileUrl = pathToFileURL(process.argv[1]).href;
|
|
25
|
+
|
|
26
|
+
if (currentFileUrl === executedFileUrl) {
|
|
27
|
+
void start();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|