create-ailk 0.1.1 → 0.3.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/component-catalog.md +210 -52
- package/dist/cli.js +0 -0
- package/dist/lib/apply-module-patches.d.ts +47 -0
- package/dist/lib/apply-module-patches.js +370 -0
- package/dist/lib/extract-module-bundle.d.ts +12 -3
- package/dist/lib/extract-module-bundle.js +236 -24
- package/dist/lib/fetch-module.d.ts +3 -38
- package/dist/lib/fetch-module.js +26 -8
- package/dist/lib/module-license-gate.d.ts +2 -0
- package/dist/lib/module-license-gate.js +1 -0
- package/dist/lib/paid-paths.d.ts +16 -0
- package/dist/lib/paid-paths.js +90 -0
- package/dist/module-architecture.d.ts +139 -23
- package/dist/module-architecture.js +668 -25
- package/dist/parse-args.d.ts +2 -2
- package/dist/parse-args.js +3 -1
- package/dist/programmatic.d.ts +2 -1
- package/dist/programmatic.js +15 -4
- package/dist/surfaces.d.ts +12 -36
- package/dist/surfaces.js +35 -5
- package/dist/sync-routes.js +31 -1
- package/package.json +14 -15
- package/templates/.claude/agents/web.md +2 -3
- package/templates/.claude/rules/architecture.md +5 -5
- package/templates/.claude/skills/README.md +2 -1
- package/templates/.env.example +21 -9
- package/templates/CONVENTIONS.md +6 -7
- package/templates/apps/api/.env.example +9 -0
- package/templates/apps/api/CLAUDE.md +35 -4
- package/templates/apps/api/package.json +0 -1
- package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
- package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
- package/templates/apps/api/src/config/index.ts +19 -0
- package/templates/apps/api/src/config/modules.ts +12 -14
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +13 -0
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
- package/templates/apps/api/src/lib/slice-load.ts +90 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
- package/templates/apps/api/src/openapi/index.ts +9 -3
- package/templates/apps/api/src/openapi/spec.ts +505 -65
- package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
- package/templates/apps/api/src/routes/content/index.ts +38 -11
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +28 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +144 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +478 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +717 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +35 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +484 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +52 -10
- package/templates/apps/api/src/routes/project-listings/me.ts +86 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
- package/templates/apps/api/src/server.ts +471 -133
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
- package/templates/apps/api/src/services/consent.ts +236 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
- package/templates/apps/api/src/services/listing-config.ts +58 -0
- package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
- package/templates/apps/api/src/services/project-listings.ts +602 -29
- package/templates/apps/api/src/vercel-handler.ts +60 -26
- package/templates/apps/mcp/.env.example +8 -0
- package/templates/apps/mcp/CLAUDE.md +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
- package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
- package/templates/apps/mcp/src/config/modules.ts +14 -13
- package/templates/apps/mcp/src/server.ts +8 -4
- package/templates/apps/mcp/src/tools/index.ts +7 -22
- package/templates/apps/web/.env.example +15 -0
- package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/layout.tsx +13 -1
- package/templates/apps/web/app/llms.txt/route.ts +12 -9
- package/templates/apps/web/jest.config.cjs +20 -13
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
- package/templates/apps/web/lib/site-brand.tsx +4 -1
- package/templates/apps/web/lib/site-theme.ts +74 -0
- package/templates/apps/web/next.config.mjs +1 -3
- package/templates/apps/web/package.json +1 -6
- 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 +6 -3
- package/templates/apps/web/public/lockup-horizontal.svg +4 -0
- package/templates/apps/web/public/logomark.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +2 -2
- package/templates/apps/web/public/wordmark.svg +4 -0
- package/templates/content/_site.mdx +12 -0
- package/templates/database/CHANGELOG.md +94 -0
- package/templates/database/inbox/schema.prisma +165 -0
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
- package/templates/database/package.json +1 -1
- package/templates/database/scripts/db-generate-locked.sh +0 -0
- package/templates/package.json +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
- package/templates/apps/api/src/__tests__/server.test.ts +0 -253
- package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
- package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
- package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
- package/templates/apps/api/src/bin/seed-presets.ts +0 -58
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
- package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
- package/templates/apps/api/src/lib/stripe.ts +0 -52
- package/templates/apps/api/src/lib/tenant-db.ts +0 -225
- package/templates/apps/api/src/lib/ws-token.ts +0 -91
- package/templates/apps/api/src/middleware/tenant.ts +0 -99
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
- package/templates/apps/api/src/routes/billing/index.ts +0 -36
- package/templates/apps/api/src/routes/billing/portal.ts +0 -182
- package/templates/apps/api/src/routes/billing/read.ts +0 -75
- package/templates/apps/api/src/routes/billing/usage.ts +0 -153
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
- package/templates/apps/api/src/routes/checkout/index.ts +0 -13
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
- package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
- package/templates/apps/api/src/routes/flows/README.md +0 -147
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
- package/templates/apps/api/src/routes/flows/index.ts +0 -202
- package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
- package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
- package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
- package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
- package/templates/apps/api/src/routes/schedule/index.ts +0 -249
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
- package/templates/apps/api/src/routes/slack/actions.ts +0 -177
- package/templates/apps/api/src/routes/slack/index.ts +0 -38
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
- package/templates/apps/api/src/routes/webhooks/README.md +0 -80
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
- package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
- package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
- package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
- package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
- package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
- package/templates/apps/api/src/services/flow-engine.ts +0 -1278
- package/templates/apps/api/src/services/lead-promotion.ts +0 -176
- package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
- package/templates/apps/api/src/services/playbook-compile.ts +0 -398
- package/templates/apps/api/src/services/playbook-render.ts +0 -263
- package/templates/apps/api/src/services/project-listing-decision.ts +0 -490
- package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
- package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
- package/templates/apps/api/src/services/recommender-capture.ts +0 -835
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
- package/templates/apps/api/src/services/scheduling/index.ts +0 -63
- package/templates/apps/api/src/services/scheduling/types.ts +0 -88
- package/templates/apps/api/src/services/tenant-context.ts +0 -451
- package/templates/apps/api/src/services/usage-metering.ts +0 -699
- package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
- package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
- package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
- package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
- package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
- package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
- package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
- package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
- package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
- package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
- package/templates/apps/web/content/en/blog/README.txt +0 -25
- package/templates/apps/web/content/en/docs/index.mdx +0 -28
- package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
- package/templates/apps/web/lib/blog-list-data.ts +0 -95
- package/templates/apps/web/lib/blog-post-data.ts +0 -79
- package/templates/apps/web/lib/source.ts +0 -34
- package/templates/apps/web/public/logomark-dark.svg +0 -4
- package/templates/apps/web/public/logomark-light.svg +0 -4
- package/templates/apps/web/source.config.ts +0 -78
|
@@ -31,39 +31,70 @@ async function getBuildFunction(): Promise<typeof buildServer> {
|
|
|
31
31
|
|
|
32
32
|
// Initialize build function
|
|
33
33
|
const buildPromise = getBuildFunction();
|
|
34
|
-
let serverInstance: FastifyServer | null = null;
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
|
-
*
|
|
36
|
+
* The in-flight (or settled) build. Caching the PROMISE rather than the
|
|
37
|
+
* resolved instance is what makes this safe: a warm Vercel container serves
|
|
38
|
+
* concurrent invocations on one Node process, and the old check-then-set
|
|
39
|
+
* (`if (!serverInstance)` followed by two awaits before the assignment) let
|
|
40
|
+
* two requests arriving during a cold start both see null and both construct
|
|
41
|
+
* a complete Fastify server — every plugin registered, every route wired, the
|
|
42
|
+
* Prisma client initialised — with one of the two then discarded. Nothing
|
|
43
|
+
* failed; the only symptoms were cold-start latency and memory (#5332).
|
|
38
44
|
*/
|
|
39
|
-
|
|
40
|
-
if (!serverInstance) {
|
|
41
|
-
logger.info('[Vercel Handler] Building server instance...');
|
|
42
|
-
const startTime = Date.now();
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const buildTimeout = new Promise<never>((_, reject) => {
|
|
46
|
-
setTimeout(() => reject(new Error('Server build timeout after 10s')), 10000);
|
|
47
|
-
});
|
|
45
|
+
let serverPromise: Promise<FastifyServer> | null = null;
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
async function buildServerInstance(): Promise<FastifyServer> {
|
|
48
|
+
logger.info('[Vercel Handler] Building server instance...');
|
|
49
|
+
const startTime = Date.now();
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
try {
|
|
52
|
+
const buildTimeout = new Promise<never>((_, reject) => {
|
|
53
|
+
setTimeout(() => reject(new Error('Server build timeout after 10s')), 10000);
|
|
54
|
+
});
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
const build = await Promise.race([buildPromise, buildTimeout]);
|
|
57
|
+
logger.info('[Vercel Handler] Build function obtained, creating server...');
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
const initTimeout = new Promise<never>((_, reject) => {
|
|
60
|
+
setTimeout(() => reject(new Error('Server initialization timeout after 10s')), 10000);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const serverInstance = await Promise.race([build(), initTimeout]);
|
|
64
|
+
|
|
65
|
+
const buildTime = Date.now() - startTime;
|
|
66
|
+
logger.info({ buildTime }, '[Vercel Handler] Server built successfully');
|
|
67
|
+
return serverInstance;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const buildTime = Date.now() - startTime;
|
|
70
|
+
logger.error({ err: error, buildTime }, '[Vercel Handler] Failed to build server');
|
|
71
|
+
throw new Error(`Server build failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
65
72
|
}
|
|
66
|
-
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get or create the Fastify server instance
|
|
77
|
+
*/
|
|
78
|
+
function getServer(): Promise<FastifyServer> {
|
|
79
|
+
if (serverPromise) {
|
|
80
|
+
return serverPromise;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const attempt = buildServerInstance();
|
|
84
|
+
serverPromise = attempt;
|
|
85
|
+
// Evict a FAILED build from the cache so the next invocation retries —
|
|
86
|
+
// a rejected promise left in place would serve every later request the
|
|
87
|
+
// same cold-start failure for the life of the container. The guard keeps
|
|
88
|
+
// this from clearing a newer attempt; `attempt` is returned as-is, so the
|
|
89
|
+
// rejection still reaches the caller (this handler only silences the
|
|
90
|
+
// unhandled-rejection warning on its own bookkeeping branch).
|
|
91
|
+
attempt.catch(() => {
|
|
92
|
+
if (serverPromise === attempt) {
|
|
93
|
+
serverPromise = null;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return attempt;
|
|
67
98
|
}
|
|
68
99
|
|
|
69
100
|
/**
|
|
@@ -156,9 +187,12 @@ export default async function handler(
|
|
|
156
187
|
logger.error({ err: error }, '[Vercel Handler] Error handling request');
|
|
157
188
|
|
|
158
189
|
if (!res.headersSent) {
|
|
190
|
+
// Generic body only (security review on #146): this catch sits above
|
|
191
|
+
// Fastify's error serializer, so error.message could carry hosts,
|
|
192
|
+
// paths, or driver detail. The logger.error above keeps the detail.
|
|
159
193
|
res.status(500).json({
|
|
160
194
|
error: 'Internal Server Error',
|
|
161
|
-
message:
|
|
195
|
+
message: 'Something went wrong on our end.',
|
|
162
196
|
});
|
|
163
197
|
}
|
|
164
198
|
}
|
|
@@ -40,6 +40,14 @@ MCP_RATE_LIMIT_WRITE_PER_MIN=10
|
|
|
40
40
|
# Example: https://app.yourdomain.com,https://other.yourdomain.com
|
|
41
41
|
MCP_CORS_WRITE_ALLOWED_ORIGINS=
|
|
42
42
|
|
|
43
|
+
# ── Paid modules (D-44, #5368) ──────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
# Comma-separated allowlist of installed paid modules, e.g.
|
|
46
|
+
# "multi-tenant,scheduling". Unset (the default) or "" means NO paid modules
|
|
47
|
+
# — installed modules enable themselves explicitly. Recognized keys: docs,
|
|
48
|
+
# multi-tenant, scheduling, content-write. Must match apps/api's value.
|
|
49
|
+
# AILK_ENABLED_MODULES=
|
|
50
|
+
|
|
43
51
|
# ── Abuse protection ───────────────────────────────────────────────────────────
|
|
44
52
|
|
|
45
53
|
# Maximum number of capture_lead submissions per IP per day.
|
|
@@ -25,8 +25,8 @@ Every tool maps 1:1 to an `apps/api` endpoint. When you change a tool, change th
|
|
|
25
25
|
**Read (9):** `list_pages`, `read_content`, `search_content`, `get_site_info`, `get_business_hours`, `get_locations`, `get_offerings`, `get_pricing`, `get_faq`
|
|
26
26
|
**Conversion-write (3):** `capture_lead`, `subscribe_newsletter`, `request_callback`
|
|
27
27
|
**Maintenance (2):** `audit`, `scaffold`
|
|
28
|
-
**Flow (2):** `get_flow`, `submit_flow_step`
|
|
29
|
-
**Booking (3):** `list_availability`, `get_event_meta`, `create_booking`
|
|
28
|
+
**Flow (2):** `get_flow`, `submit_flow_step` — module-gated (`module: "signal-collectors"`, D-48, #5366): identity-blind and free of any `requirePaidTier()` call like every tool in this list, but registered only when the signal-collectors module is present, the same shape Booking below already has
|
|
29
|
+
**Booking (3):** `list_availability`, `get_event_meta`, `create_booking` — module-gated (`module: "scheduling"`)
|
|
30
30
|
|
|
31
31
|
### Why the catalog reports 23 and the server registers 22
|
|
32
32
|
|
|
@@ -24,7 +24,28 @@ interface RegisteredTool {
|
|
|
24
24
|
description?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// The catalog includes module-gated tools (scheduling, content-write,
|
|
28
|
+
// signal-collectors); an unset AILK_ENABLED_MODULES no longer registers them
|
|
29
|
+
// by default (D-44, #5368), so this drift check enables every paid module
|
|
30
|
+
// explicitly — it is comparing the full catalog, not what a default build
|
|
31
|
+
// registers.
|
|
32
|
+
const PAID_MODULES = "scheduling,content-write,signal-collectors";
|
|
33
|
+
|
|
27
34
|
describe("MCP catalog ↔ server drift", () => {
|
|
35
|
+
const priorEnabledModules = process.env.AILK_ENABLED_MODULES;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
process.env.AILK_ENABLED_MODULES = PAID_MODULES;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterAll(() => {
|
|
42
|
+
if (priorEnabledModules === undefined) {
|
|
43
|
+
delete process.env.AILK_ENABLED_MODULES;
|
|
44
|
+
} else {
|
|
45
|
+
process.env.AILK_ENABLED_MODULES = priorEnabledModules;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
28
49
|
it("registered tool names equal catalog tool names", () => {
|
|
29
50
|
const server = createMcpServer();
|
|
30
51
|
const registered = (
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `isModuleEnabled()` default-flip regression test (D-44, #5368).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `apps/api/src/config/__tests__/modules.test.ts` — apps/* may not
|
|
5
|
+
* import apps/*, so this reader (and its test) are duplicated rather than
|
|
6
|
+
* shared. Proves the mechanical claim #5368 exists to make true: an UNSET
|
|
7
|
+
* `AILK_ENABLED_MODULES` now gates every paid module OFF, the same as an
|
|
8
|
+
* explicit empty string — only an explicit allowlist turns modules on, and
|
|
9
|
+
* turns on exactly the modules it names.
|
|
10
|
+
*
|
|
11
|
+
* Reverting `enabledModules()`'s unset branch back to the `undefined`
|
|
12
|
+
* sentinel (S1, #5088's original shape) makes the "unset" cases below fail
|
|
13
|
+
* RED — see #5368 verification notes.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { isModuleEnabled } from "../../src/config/modules.js";
|
|
17
|
+
|
|
18
|
+
const RECOGNIZED_MODULES = [
|
|
19
|
+
"docs",
|
|
20
|
+
"multi-tenant",
|
|
21
|
+
"scheduling",
|
|
22
|
+
"content-write",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
describe("isModuleEnabled", () => {
|
|
26
|
+
const originalEnv = process.env.AILK_ENABLED_MODULES;
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
if (originalEnv === undefined) {
|
|
30
|
+
delete process.env.AILK_ENABLED_MODULES;
|
|
31
|
+
} else {
|
|
32
|
+
process.env.AILK_ENABLED_MODULES = originalEnv;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("AILK_ENABLED_MODULES unset", () => {
|
|
37
|
+
it.each(RECOGNIZED_MODULES)("returns false for %s", (moduleName) => {
|
|
38
|
+
delete process.env.AILK_ENABLED_MODULES;
|
|
39
|
+
expect(isModuleEnabled(moduleName)).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('AILK_ENABLED_MODULES=""', () => {
|
|
44
|
+
it.each(RECOGNIZED_MODULES)("returns false for %s", (moduleName) => {
|
|
45
|
+
process.env.AILK_ENABLED_MODULES = "";
|
|
46
|
+
expect(isModuleEnabled(moduleName)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("AILK_ENABLED_MODULES as an explicit allowlist", () => {
|
|
51
|
+
it.each(RECOGNIZED_MODULES)(
|
|
52
|
+
"enables exactly the listed modules — %s on when named",
|
|
53
|
+
(moduleName) => {
|
|
54
|
+
process.env.AILK_ENABLED_MODULES = moduleName;
|
|
55
|
+
expect(isModuleEnabled(moduleName)).toBe(true);
|
|
56
|
+
for (const other of RECOGNIZED_MODULES) {
|
|
57
|
+
if (other === moduleName) continue;
|
|
58
|
+
expect(isModuleEnabled(other)).toBe(false);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
it("enables every module named in a comma list, and no others", () => {
|
|
64
|
+
process.env.AILK_ENABLED_MODULES = "multi-tenant,scheduling";
|
|
65
|
+
expect(isModuleEnabled("multi-tenant")).toBe(true);
|
|
66
|
+
expect(isModuleEnabled("scheduling")).toBe(true);
|
|
67
|
+
expect(isModuleEnabled("docs")).toBe(false);
|
|
68
|
+
expect(isModuleEnabled("content-write")).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("trims whitespace and drops empty entries, same as before the flip", () => {
|
|
72
|
+
process.env.AILK_ENABLED_MODULES = " multi-tenant , , scheduling ";
|
|
73
|
+
expect(isModuleEnabled("multi-tenant")).toBe(true);
|
|
74
|
+
expect(isModuleEnabled("scheduling")).toBe(true);
|
|
75
|
+
expect(isModuleEnabled("docs")).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -18,7 +18,7 @@ import { getMcpToolRegistrations } from "@working-theory/metadata";
|
|
|
18
18
|
|
|
19
19
|
import { createMcpServer, listRegisteredTools } from "../src/server.js";
|
|
20
20
|
|
|
21
|
-
const PAID_MODULES = ["scheduling", "content-write"] as const;
|
|
21
|
+
const PAID_MODULES = ["scheduling", "content-write", "signal-collectors"] as const;
|
|
22
22
|
|
|
23
23
|
function withEnabledModules<T>(enabled: string, fn: () => T): T {
|
|
24
24
|
const prior = process.env.AILK_ENABLED_MODULES;
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Paid-module presence — the registration-grain signal used by `server.ts`
|
|
3
3
|
* to decide whether a paid module's tools are wired into this build
|
|
4
|
-
* (S1, #5088
|
|
5
|
-
*
|
|
4
|
+
* (S1, #5088; defaulted off by D-44, #5368). Mirrors
|
|
5
|
+
* `apps/api/src/config/modules.ts` — apps/* may not import apps/*, so this
|
|
6
|
+
* tiny reader is duplicated rather than shared.
|
|
6
7
|
*
|
|
7
8
|
* `AILK_ENABLED_MODULES` is a comma-separated allowlist (e.g.
|
|
8
|
-
* `"multi-tenant,scheduling"`). Unset means
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* `"multi-tenant,scheduling"`). Unset means **no paid modules** — installed
|
|
10
|
+
* modules enable themselves explicitly; an explicit empty string (`""`)
|
|
11
|
+
* means the same (the OSS-strip build target); a list enables exactly the
|
|
12
|
+
* modules it names.
|
|
11
13
|
*
|
|
12
|
-
* Recognized module keys: `docs`, `multi-tenant`, `scheduling
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
14
|
+
* Recognized module keys: `docs`, `multi-tenant`, `scheduling`,
|
|
15
|
+
* `signal-collectors` (the `ModuleName` vocabulary
|
|
16
|
+
* `packages/create-ailk/src/module-architecture.ts` already models via
|
|
17
|
+
* `MODULE_SLICES`) plus `content-write` — the Pro
|
|
15
18
|
* content-write surface (`create_page`/`update_page`/`delete_page`).
|
|
16
19
|
* `content-write` is intentionally NOT a `MODULE_SLICES` entry: it is not
|
|
17
20
|
* (yet) a create-ailk `--modules` selector, so it stays a local flag here
|
|
18
21
|
* rather than widening the create-ailk module vocabulary.
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
|
-
function enabledModules(): Set<string>
|
|
24
|
+
function enabledModules(): Set<string> {
|
|
22
25
|
const raw = process.env.AILK_ENABLED_MODULES;
|
|
23
|
-
if (raw === undefined) return
|
|
26
|
+
if (raw === undefined) return new Set(); // unset = no paid modules
|
|
24
27
|
return new Set(
|
|
25
28
|
raw
|
|
26
29
|
.split(",")
|
|
@@ -31,7 +34,5 @@ function enabledModules(): Set<string> | undefined {
|
|
|
31
34
|
|
|
32
35
|
/** Whether the named paid module's tools should register on this build/boot. */
|
|
33
36
|
export function isModuleEnabled(moduleName: string): boolean {
|
|
34
|
-
|
|
35
|
-
if (enabled === undefined) return true;
|
|
36
|
-
return enabled.has(moduleName);
|
|
37
|
+
return enabledModules().has(moduleName);
|
|
37
38
|
}
|
|
@@ -20,7 +20,6 @@ import { ailkAudit } from "./tools/audit.js";
|
|
|
20
20
|
import { ailkCaptureLead } from "./tools/capture_lead.js";
|
|
21
21
|
import { ailkGetBusinessHours } from "./tools/get_business_hours.js";
|
|
22
22
|
import { ailkGetFaq } from "./tools/get_faq.js";
|
|
23
|
-
import { ailkGetFlow } from "./tools/get_flow.js";
|
|
24
23
|
import { ailkGetLocations } from "./tools/get_locations.js";
|
|
25
24
|
import { ailkGetOfferings } from "./tools/get_offerings.js";
|
|
26
25
|
import { ailkGetPricing } from "./tools/get_pricing.js";
|
|
@@ -31,7 +30,6 @@ import { ailkReadContent } from "./tools/read_content.js";
|
|
|
31
30
|
import { ailkRequestCallback } from "./tools/request_callback.js";
|
|
32
31
|
import { ailkScaffold } from "./tools/scaffold.js";
|
|
33
32
|
import { ailkSearchContent } from "./tools/search_content.js";
|
|
34
|
-
import { ailkSubmitFlowStep } from "./tools/submit_flow_step.js";
|
|
35
33
|
import { ailkSubscribeNewsletter } from "./tools/subscribe_newsletter.js";
|
|
36
34
|
|
|
37
35
|
/**
|
|
@@ -65,8 +63,6 @@ const HANDLERS: Record<string, (input: unknown) => Promise<unknown>> = {
|
|
|
65
63
|
subscribe_newsletter: (input) => ailkSubscribeNewsletter(input as never),
|
|
66
64
|
request_callback: (input) => ailkRequestCallback(input as never),
|
|
67
65
|
scaffold: (input) => ailkScaffold(input as never),
|
|
68
|
-
get_flow: (input) => ailkGetFlow(input as never),
|
|
69
|
-
submit_flow_step: (input) => ailkSubmitFlowStep(input as never),
|
|
70
66
|
};
|
|
71
67
|
|
|
72
68
|
type ToolHandler = (input: unknown) => Promise<unknown>;
|
|
@@ -119,6 +115,14 @@ const MODULE_TOOL_ENTRIES: Record<
|
|
|
119
115
|
create_page: { specifier: "./tools/create_page.js", exportName: "ailkCreatePage" },
|
|
120
116
|
update_page: { specifier: "./tools/update_page.js", exportName: "ailkUpdatePage" },
|
|
121
117
|
delete_page: { specifier: "./tools/delete_page.js", exportName: "ailkDeletePage" },
|
|
118
|
+
// D-48 signal-collectors — the flow engine's MCP twins of GET
|
|
119
|
+
// /v1/flows/:slug and POST /v1/flows/:slug/steps. Anonymous, identity-blind;
|
|
120
|
+
// free-anonymous status is now module-gated rather than unconditional.
|
|
121
|
+
get_flow: { specifier: "./tools/get_flow.js", exportName: "ailkGetFlow" },
|
|
122
|
+
submit_flow_step: {
|
|
123
|
+
specifier: "./tools/submit_flow_step.js",
|
|
124
|
+
exportName: "ailkSubmitFlowStep",
|
|
125
|
+
},
|
|
122
126
|
};
|
|
123
127
|
|
|
124
128
|
/**
|
|
@@ -124,27 +124,12 @@ export {
|
|
|
124
124
|
type ScaffoldOutput,
|
|
125
125
|
} from "./scaffold.js";
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
export {
|
|
136
|
-
submitFlowStepInputSchemaForTool,
|
|
137
|
-
submitFlowStepOutputSchema,
|
|
138
|
-
ailkSubmitFlowStep,
|
|
139
|
-
type SubmitFlowStepInput,
|
|
140
|
-
type AilkSubmitFlowStepOutput,
|
|
141
|
-
} from "./submit_flow_step.js";
|
|
142
|
-
|
|
143
|
-
// The scheduling tools (list_availability, get_event_meta, create_booking) are
|
|
144
|
-
// deliberately NOT re-exported here, and neither are the content-write tools
|
|
145
|
-
// (create_page, update_page, delete_page). Both groups are module-conditional
|
|
146
|
-
// slice files (#5182): a re-export from this barrel is a static path into a
|
|
147
|
-
// file the OSS strip removes, which reds the public tree's type-check. They are
|
|
148
|
-
// reached only through `createMcpServer`'s per-module loader in ../server.ts.
|
|
127
|
+
// The scheduling tools (list_availability, get_event_meta, create_booking),
|
|
128
|
+
// the content-write tools (create_page, update_page, delete_page), and the
|
|
129
|
+
// signal-collectors flow tools (get_flow, submit_flow_step) are deliberately
|
|
130
|
+
// NOT re-exported here. All three groups are module-conditional slice files
|
|
131
|
+
// (#5182): a re-export from this barrel is a static path into a file the OSS
|
|
132
|
+
// strip removes, which reds the public tree's type-check. They are reached
|
|
133
|
+
// only through `createMcpServer`'s per-module loader in ../server.ts.
|
|
149
134
|
// The rule this barrel now follows: it re-exports exactly the tools no module
|
|
150
135
|
// gate can remove.
|
|
@@ -26,6 +26,21 @@ CONTENT_BACKEND=mdx
|
|
|
26
26
|
# Same convention as apps/workspace/lib/client-api.ts's identically-named var.
|
|
27
27
|
# Should be apps/api's origin with NO trailing slash (a single trailing
|
|
28
28
|
# slash is stripped before joining, but keep this clean regardless).
|
|
29
|
+
#
|
|
30
|
+
# THIS IS THE ONE BROWSER-SIDE NAME (#5397). All three browser readers of the
|
|
31
|
+
# apps/api origin resolve it from this variable: resolveSubmitUrl
|
|
32
|
+
# (packages/ui, the form blocks above), apps/workspace's client-api, and
|
|
33
|
+
# @working-theory/api-client's getApiBase() — which runs in the browser
|
|
34
|
+
# wherever a "use client" component binds it, e.g. the founder application's
|
|
35
|
+
# ListingApplicationClient. getApiBase() previously read NEXT_PUBLIC_API_URL,
|
|
36
|
+
# a name this file never documented, so a site configured exactly per these
|
|
37
|
+
# docs sent its browser traffic to http://localhost:3001. That name is still
|
|
38
|
+
# honoured as a deprecated fallback; do not set it in a new deployment.
|
|
39
|
+
#
|
|
40
|
+
# The server-side counterpart is AILK_API_URL (see the repo-root .env.example)
|
|
41
|
+
# — deliberately NOT NEXT_PUBLIC_, and it still wins over this one when both
|
|
42
|
+
# are set, so an RSC/Server-Action call can target an internal origin the
|
|
43
|
+
# browser never sees.
|
|
29
44
|
# NEXT_PUBLIC_AILK_API_URL=http://localhost:3002
|
|
30
45
|
|
|
31
46
|
# =============================================================================
|
package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx
RENAMED
|
@@ -24,14 +24,14 @@ jest.mock("next/headers", () => ({
|
|
|
24
24
|
|
|
25
25
|
const mockGetSession = jest.fn();
|
|
26
26
|
// eslint-disable-next-line no-restricted-syntax -- #507: mock-internal rewrite pending; auth.ts's lazy Better-Auth singleton has no HTTP/DB boundary a unit test of the session-branch can mock without standing up Prisma.
|
|
27
|
-
jest.mock("
|
|
27
|
+
jest.mock("../../../../auth", () => ({
|
|
28
28
|
__esModule: true,
|
|
29
29
|
auth: { api: { getSession: (...args: unknown[]) => mockGetSession(...args) } },
|
|
30
30
|
}));
|
|
31
31
|
|
|
32
32
|
const mockGetActiveProviderIds = jest.fn();
|
|
33
33
|
// eslint-disable-next-line no-restricted-syntax -- #507: mock-internal rewrite pending; auth-env.ts is a pure env-introspection module with no external boundary to mock instead.
|
|
34
|
-
jest.mock("
|
|
34
|
+
jest.mock("../../../../lib/auth-env", () => ({
|
|
35
35
|
__esModule: true,
|
|
36
36
|
getActiveProviderIds: () => mockGetActiveProviderIds(),
|
|
37
37
|
}));
|
|
@@ -48,8 +48,8 @@ jest.mock("better-auth/react", () => ({
|
|
|
48
48
|
createAuthClient: () => ({ signIn: { social: mockSignInSocial } }),
|
|
49
49
|
}));
|
|
50
50
|
|
|
51
|
-
import AuthedLayout from "
|
|
52
|
-
import { SignInGate } from "
|
|
51
|
+
import AuthedLayout from "../layout";
|
|
52
|
+
import { SignInGate } from "../sign-in-gate";
|
|
53
53
|
|
|
54
54
|
describe("AuthedLayout — the (authed) session guard", () => {
|
|
55
55
|
beforeEach(() => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// oss: false
|
|
1
2
|
"use client";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -53,7 +54,7 @@ import {
|
|
|
53
54
|
type FlowStepperMessages,
|
|
54
55
|
type FlowStepSubmitPayload,
|
|
55
56
|
type FlowStepSubmitResult,
|
|
56
|
-
} from "@working-theory/ui";
|
|
57
|
+
} from "@working-theory/ui/slices/signal-collectors";
|
|
57
58
|
import type { AeoScoreResult, FlowConfig, FlowState } from "@working-theory/validation";
|
|
58
59
|
import {
|
|
59
60
|
deriveFlags,
|
package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// oss: false
|
|
1
2
|
/**
|
|
2
3
|
* Tests for FlowStepperClient's permalink completion redirect on a
|
|
3
4
|
* `mode:"recommending"` flow (#4770 D1/D7/D12).
|
|
@@ -64,7 +65,7 @@ interface MockFlowStepperProps {
|
|
|
64
65
|
|
|
65
66
|
let captured: MockFlowStepperProps | undefined;
|
|
66
67
|
|
|
67
|
-
jest.mock("@working-theory/ui", () => ({
|
|
68
|
+
jest.mock("@working-theory/ui/slices/signal-collectors", () => ({
|
|
68
69
|
FlowStepper: (props: MockFlowStepperProps) => {
|
|
69
70
|
captured = props;
|
|
70
71
|
return require("react").createElement("div", {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// oss: false
|
|
1
2
|
/**
|
|
2
3
|
* Tests for FlowStepperClient's segment-keyed completion redirect (#3960 D5).
|
|
3
4
|
*
|
|
@@ -68,7 +69,7 @@ const PII_PAYLOAD = {
|
|
|
68
69
|
|
|
69
70
|
let capturedOnCompleted: MockFlowStepperProps["onCompleted"];
|
|
70
71
|
|
|
71
|
-
jest.mock("@working-theory/ui", () => ({
|
|
72
|
+
jest.mock("@working-theory/ui/slices/signal-collectors", () => ({
|
|
72
73
|
FlowStepper: (props: MockFlowStepperProps) => {
|
|
73
74
|
capturedOnCompleted = props.onCompleted;
|
|
74
75
|
async function handleSubmit(): Promise<void> {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// oss: false
|
|
1
2
|
"use server";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
*/
|
|
30
31
|
|
|
31
32
|
import { flows } from "@working-theory/api-client";
|
|
32
|
-
import type { FlowStepSubmitResult } from "@working-theory/ui";
|
|
33
|
+
import type { FlowStepSubmitResult } from "@working-theory/ui/slices/signal-collectors";
|
|
33
34
|
import {
|
|
34
35
|
flowSlugSchema,
|
|
35
36
|
submitFlowStepInputSchema,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Chrome extract — Nav + Footer from @working-theory/ui (#1451)
|
|
5
5
|
// Shell/Chrome composition — Nav + Footer wrap via @working-theory/templates MarketingShell (#2820)
|
|
6
6
|
// Session-aware Nav actions — Sign in / Account + Sign out from the server-read session (#4984)
|
|
7
|
+
// Site-declared theme — `_site.mdx` `theme:` block → token overlay → CSS vars
|
|
7
8
|
//
|
|
8
9
|
// Sits under app/[locale]/ and sets the <html lang> attribute to the
|
|
9
10
|
// active locale. next-intl's NextIntlClientProvider is wired here so
|
|
@@ -48,6 +49,7 @@ import { SignOutTrigger } from "../../components/sign-out-trigger";
|
|
|
48
49
|
import { navItems } from "../../lib/nav-config";
|
|
49
50
|
import { getOgImageUrl } from "../../lib/og";
|
|
50
51
|
import { SITE_NAME, siteLogo } from "../../lib/site-brand";
|
|
52
|
+
import { loadSiteBrandTokens } from "../../lib/site-theme";
|
|
51
53
|
import { resolveBaseUrl } from "../../src/lib/routes";
|
|
52
54
|
|
|
53
55
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
@@ -238,11 +240,21 @@ export default async function LocaleLayout({
|
|
|
238
240
|
// Shared brand mark injected into both Nav and Footer chrome slots.
|
|
239
241
|
const brandLogo = siteLogo();
|
|
240
242
|
|
|
243
|
+
// The site's own declared colours, read from `content/_site.mdx`'s `theme:`
|
|
244
|
+
// block and translated from that customer-facing vocabulary onto a token
|
|
245
|
+
// overlay (`siteThemeToBrandTokens`). `undefined` when the site declares no
|
|
246
|
+
// theme — which is the regression floor: no `brand` key is passed, the
|
|
247
|
+
// resolver takes its untouched fast path, and the emitted CSS is
|
|
248
|
+
// byte-for-byte what it was before this wiring existed.
|
|
249
|
+
const siteBrandTokens = loadSiteBrandTokens();
|
|
250
|
+
|
|
241
251
|
// Both theme modes, gated by [data-theme="dark"] (#1532 — sibling fix; the
|
|
242
252
|
// single-mode call inlined an unconditional :root block that never
|
|
243
253
|
// responded to a client-side data-theme toggle). themeMode still drives
|
|
244
254
|
// the initial data-theme attribute below for a no-flash first paint.
|
|
245
|
-
const inlineThemeStyle = themeToCssVarsBothModes(
|
|
255
|
+
const inlineThemeStyle = themeToCssVarsBothModes(
|
|
256
|
+
siteBrandTokens ? { brand: siteBrandTokens } : {},
|
|
257
|
+
);
|
|
246
258
|
|
|
247
259
|
return (
|
|
248
260
|
<html lang={locale} dir={isRtl(locale) ? "rtl" : "ltr"} data-theme={themeMode}>
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* Returns a plain-text index of AILK's content surface for LLM consumption,
|
|
5
5
|
* following the llms.txt convention (https://llmstxt.org).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Indexes the section catalog (MDX seeds in content/) via
|
|
8
|
+
* @working-theory/content-adapters. Nothing is gated: a Free site has no blog
|
|
9
|
+
* (D-46), so there is no blog corpus to omit — `getBlogPages` defaults to an
|
|
10
|
+
* empty list and the docs + blog module ships its own variant of this route
|
|
11
|
+
* that reads `blogSource` (#5365 D9).
|
|
10
12
|
*
|
|
11
13
|
* Format per entry:
|
|
12
14
|
* - Title: <title>
|
|
@@ -14,8 +16,9 @@
|
|
|
14
16
|
* Description: <description>
|
|
15
17
|
*
|
|
16
18
|
* DI note: `createLlmsHandler` accepts optional `getContentAdapter` and
|
|
17
|
-
* `getBlogPages` deps
|
|
18
|
-
*
|
|
19
|
+
* `getBlogPages` deps — tests inject stubs, and the module's variant of this
|
|
20
|
+
* route supplies the real `blogSource` reader. The exported `GET` is
|
|
21
|
+
* unchanged either way.
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
import { SCHEMA_ORG_TYPES } from "@working-theory/content-adapters";
|
|
@@ -23,7 +26,6 @@ import type { ContentAdapter, SchemaOrgType } from "@working-theory/content-adap
|
|
|
23
26
|
import { getContentAdapter as defaultGetContentAdapter } from "@working-theory/page-renderer";
|
|
24
27
|
|
|
25
28
|
import { SITE_NAME } from "../../lib/site-brand";
|
|
26
|
-
import { blogSource as defaultBlogSource } from "../../lib/source";
|
|
27
29
|
import { resolveBaseUrl, slugToPath } from "../../src/lib/routes";
|
|
28
30
|
|
|
29
31
|
export interface LlmsHandlerDeps {
|
|
@@ -33,8 +35,9 @@ export interface LlmsHandlerDeps {
|
|
|
33
35
|
*/
|
|
34
36
|
getContentAdapter?: () => ContentAdapter;
|
|
35
37
|
/**
|
|
36
|
-
* Blog pages getter. Defaults to
|
|
37
|
-
*
|
|
38
|
+
* Blog pages getter. Defaults to none — Free has no blog (D-46, #5365 D9).
|
|
39
|
+
* The docs + blog module's own copy of this route reads `blogSource`.
|
|
40
|
+
* Override in tests to inject stub pages.
|
|
38
41
|
*/
|
|
39
42
|
getBlogPages?: () => Array<{ url: string; data: { title: string; description?: string } }>;
|
|
40
43
|
}
|
|
@@ -45,7 +48,7 @@ export interface LlmsHandlerDeps {
|
|
|
45
48
|
*/
|
|
46
49
|
export function createLlmsHandler(deps: LlmsHandlerDeps = {}) {
|
|
47
50
|
const resolveAdapter = deps.getContentAdapter ?? defaultGetContentAdapter;
|
|
48
|
-
const resolveBlogPages = deps.getBlogPages ?? (() =>
|
|
51
|
+
const resolveBlogPages = deps.getBlogPages ?? (() => []);
|
|
49
52
|
|
|
50
53
|
return async function llmsGET(): Promise<Response> {
|
|
51
54
|
const adapter = resolveAdapter();
|