@se-studio/ab-testing 0.0.0-next16-20260822103555

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +1125 -0
  2. package/README.md +412 -0
  3. package/dist/codegen/cli.d.ts +3 -0
  4. package/dist/codegen/cli.d.ts.map +1 -0
  5. package/dist/codegen/cli.js +121 -0
  6. package/dist/codegen/cli.js.map +1 -0
  7. package/dist/codegen/generator.d.ts +42 -0
  8. package/dist/codegen/generator.d.ts.map +1 -0
  9. package/dist/codegen/generator.js +56 -0
  10. package/dist/codegen/generator.js.map +1 -0
  11. package/dist/codegen/index.d.ts +9 -0
  12. package/dist/codegen/index.d.ts.map +1 -0
  13. package/dist/codegen/index.js +8 -0
  14. package/dist/codegen/index.js.map +1 -0
  15. package/dist/components/AbTestReporter.d.ts +2 -0
  16. package/dist/components/AbTestReporter.d.ts.map +1 -0
  17. package/dist/components/AbTestReporter.js +19 -0
  18. package/dist/components/AbTestReporter.js.map +1 -0
  19. package/dist/components/AbTestUtmScript.d.ts +17 -0
  20. package/dist/components/AbTestUtmScript.d.ts.map +1 -0
  21. package/dist/components/AbTestUtmScript.js +43 -0
  22. package/dist/components/AbTestUtmScript.js.map +1 -0
  23. package/dist/components/index.d.ts +3 -0
  24. package/dist/components/index.d.ts.map +1 -0
  25. package/dist/components/index.js +3 -0
  26. package/dist/components/index.js.map +1 -0
  27. package/dist/hooks/index.d.ts +8 -0
  28. package/dist/hooks/index.d.ts.map +1 -0
  29. package/dist/hooks/index.js +7 -0
  30. package/dist/hooks/index.js.map +1 -0
  31. package/dist/hooks/useAbTestAssignments.d.ts +56 -0
  32. package/dist/hooks/useAbTestAssignments.d.ts.map +1 -0
  33. package/dist/hooks/useAbTestAssignments.js +87 -0
  34. package/dist/hooks/useAbTestAssignments.js.map +1 -0
  35. package/dist/index.d.ts +17 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +17 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/middleware/assignment.d.ts +31 -0
  40. package/dist/middleware/assignment.d.ts.map +1 -0
  41. package/dist/middleware/assignment.js +125 -0
  42. package/dist/middleware/assignment.js.map +1 -0
  43. package/dist/middleware/cache.d.ts +30 -0
  44. package/dist/middleware/cache.d.ts.map +1 -0
  45. package/dist/middleware/cache.js +140 -0
  46. package/dist/middleware/cache.js.map +1 -0
  47. package/dist/middleware/cookies.d.ts +44 -0
  48. package/dist/middleware/cookies.d.ts.map +1 -0
  49. package/dist/middleware/cookies.js +66 -0
  50. package/dist/middleware/cookies.js.map +1 -0
  51. package/dist/middleware/handler.d.ts +53 -0
  52. package/dist/middleware/handler.d.ts.map +1 -0
  53. package/dist/middleware/handler.js +201 -0
  54. package/dist/middleware/handler.js.map +1 -0
  55. package/dist/middleware/index.d.ts +15 -0
  56. package/dist/middleware/index.d.ts.map +1 -0
  57. package/dist/middleware/index.js +13 -0
  58. package/dist/middleware/index.js.map +1 -0
  59. package/dist/middleware/prune.d.ts +13 -0
  60. package/dist/middleware/prune.d.ts.map +1 -0
  61. package/dist/middleware/prune.js +29 -0
  62. package/dist/middleware/prune.js.map +1 -0
  63. package/dist/middleware/types.d.ts +103 -0
  64. package/dist/middleware/types.d.ts.map +1 -0
  65. package/dist/middleware/types.js +2 -0
  66. package/dist/middleware/types.js.map +1 -0
  67. package/dist/middleware/urlParams.d.ts +7 -0
  68. package/dist/middleware/urlParams.d.ts.map +1 -0
  69. package/dist/middleware/urlParams.js +20 -0
  70. package/dist/middleware/urlParams.js.map +1 -0
  71. package/dist/middleware/variantId.d.ts +9 -0
  72. package/dist/middleware/variantId.d.ts.map +1 -0
  73. package/dist/middleware/variantId.js +12 -0
  74. package/dist/middleware/variantId.js.map +1 -0
  75. package/dist/reporting/buildExperimentImpression.d.ts +6 -0
  76. package/dist/reporting/buildExperimentImpression.d.ts.map +1 -0
  77. package/dist/reporting/buildExperimentImpression.js +22 -0
  78. package/dist/reporting/buildExperimentImpression.js.map +1 -0
  79. package/dist/types.d.ts +144 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +7 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/utils.d.ts +12 -0
  84. package/dist/utils.d.ts.map +1 -0
  85. package/dist/utils.js +20 -0
  86. package/dist/utils.js.map +1 -0
  87. package/dist/webhook/handler.d.ts +109 -0
  88. package/dist/webhook/handler.d.ts.map +1 -0
  89. package/dist/webhook/handler.js +128 -0
  90. package/dist/webhook/handler.js.map +1 -0
  91. package/dist/webhook/index.d.ts +8 -0
  92. package/dist/webhook/index.d.ts.map +1 -0
  93. package/dist/webhook/index.js +7 -0
  94. package/dist/webhook/index.js.map +1 -0
  95. package/docs/llms.md +106 -0
  96. package/package.json +91 -0
package/README.md ADDED
@@ -0,0 +1,412 @@
1
+ # @se-studio/ab-testing
2
+
3
+ Server-side A/B testing framework for Next.js applications with Contentful CMS.
4
+
5
+ ## Features
6
+
7
+ - **Server-side rendering**: No flicker or layout shift - variants are rendered on the server
8
+ - **Edge-optimized**: Middleware runs at the edge for minimal latency
9
+ - **CMS-driven**: Test configuration managed entirely in Contentful
10
+ - **Provider-agnostic**: Bring your own blob storage (Vercel KV, Netlify Blobs, etc.)
11
+ - **Flexible analytics**: Use the `useAbTestAssignments` hook to integrate with any analytics platform
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
17
+ │ Contentful │────▶│ Webhook Handler │────▶│ Blob Store │
18
+ │ PageTest │ │ (API Route) │ │ (Project- │
19
+ │ Entries │ │ │ │ specific) │
20
+ └─────────────────┘ └──────────────────┘ └────────┬────────┘
21
+
22
+
23
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
24
+ │ User Request │────▶│ Middleware │────▶│ Variant Page │
25
+ │ /pricing │ │ (Edge) │ │ (SSR) │
26
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
27
+
28
+
29
+ ┌──────────────────┐
30
+ │ Cookie Set │
31
+ │ ab-test-info │
32
+ └────────┬─────────┘
33
+
34
+
35
+ ┌──────────────────┐
36
+ │ useAbTestAssign- │
37
+ │ ments Hook │
38
+ │ → Your Analytics │
39
+ └──────────────────┘
40
+ ```
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pnpm add @se-studio/ab-testing
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ### 1. Implement Your Blob Store
51
+
52
+ The package requires you to implement the `IBlobStore<AbTest>` interface for your hosting platform.
53
+
54
+ **Vercel KV Example:**
55
+
56
+ ```typescript
57
+ // src/server/abTestStore.ts
58
+ import { kv } from '@vercel/kv';
59
+ import type { IBlobStore, AbTest } from '@se-studio/ab-testing';
60
+
61
+ const STORE_KEY = 'ab-tests';
62
+
63
+ export function getAbTestStore(): IBlobStore<AbTest> {
64
+ return {
65
+ async get(key) {
66
+ const data = await kv.hget<AbTest>(STORE_KEY, key);
67
+ return data ?? undefined;
68
+ },
69
+ async set(key, value) {
70
+ await kv.hset(STORE_KEY, { [key]: value });
71
+ },
72
+ async bulkWrite(entries) {
73
+ await kv.del(STORE_KEY);
74
+ if (entries.length > 0) {
75
+ const data = Object.fromEntries(entries);
76
+ await kv.hset(STORE_KEY, data);
77
+ }
78
+ },
79
+ async size() {
80
+ return kv.hlen(STORE_KEY);
81
+ },
82
+ async values() {
83
+ const data = await kv.hgetall<Record<string, AbTest>>(STORE_KEY);
84
+ return data ? Object.values(data) : [];
85
+ },
86
+ };
87
+ }
88
+ ```
89
+
90
+ **Netlify Blobs Example:**
91
+
92
+ ```typescript
93
+ // src/server/abTestStore.ts
94
+ import { getStore } from '@netlify/blobs';
95
+ import type { IBlobStore, AbTest } from '@se-studio/ab-testing';
96
+
97
+ export function getAbTestStore(): IBlobStore<AbTest> {
98
+ const store = getStore('ab-testing');
99
+ const BLOB_NAME = 'config';
100
+
101
+ return {
102
+ async get(key) {
103
+ const data = await store.get(BLOB_NAME, { type: 'json' });
104
+ return data?.[key];
105
+ },
106
+ async set(key, value) {
107
+ const data = (await store.get(BLOB_NAME, { type: 'json' })) ?? {};
108
+ data[key] = value;
109
+ await store.setJSON(BLOB_NAME, data);
110
+ },
111
+ async bulkWrite(entries) {
112
+ const data = Object.fromEntries(entries);
113
+ await store.setJSON(BLOB_NAME, data);
114
+ },
115
+ async size() {
116
+ const data = await store.get(BLOB_NAME, { type: 'json' });
117
+ return data ? Object.keys(data).length : 0;
118
+ },
119
+ async values() {
120
+ const data = await store.get(BLOB_NAME, { type: 'json' });
121
+ return data ? Object.values(data) : [];
122
+ },
123
+ };
124
+ }
125
+ ```
126
+
127
+ ### 2. Set Up the Middleware
128
+
129
+ ```typescript
130
+ // src/proxy.ts (Next 16) or src/middleware.ts (Next 15)
131
+ import { createAbTestMiddleware } from '@se-studio/ab-testing/middleware';
132
+ import { getAbTestStore } from './server/abTestStore';
133
+ import { NextResponse } from 'next/server';
134
+ import type { NextRequest } from 'next/server';
135
+
136
+ const abTestHandler = createAbTestMiddleware({
137
+ getStore: getAbTestStore,
138
+ cacheTtlMs: 60000, // 60 second cache
139
+ });
140
+
141
+ export async function proxy(request: NextRequest) {
142
+ // Skip static assets, API routes, etc.
143
+ if (request.nextUrl.pathname.startsWith('/_next') ||
144
+ request.nextUrl.pathname.startsWith('/api')) {
145
+ return NextResponse.next();
146
+ }
147
+
148
+ // Process A/B tests
149
+ const abResponse = await abTestHandler(request);
150
+ if (abResponse) return abResponse;
151
+
152
+ return NextResponse.next();
153
+ }
154
+
155
+ export const config = {
156
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
157
+ };
158
+ ```
159
+
160
+ #### Page-test route (static rebuild pattern)
161
+
162
+ When using `createStaticAbTestMiddleware` with `variantPathPrefix: '/page-test'`, add a `/page-test/[...slugs]` route that rewrites serve variant content on the control URL. Use `findCanonicalPath(testsByPath, variantSlug)` to resolve the user-facing path, then call the paired handlers from `@se-studio/core-ui/server`:
163
+
164
+ - `generatePageTestMetadata` — control `indexed` and canonical path in `<head>`
165
+ - `generatePageTest` — variant body with control identity for breadcrumbs, `href`, and JSON-LD
166
+
167
+ See the `se-marketing-sites-create-page` skill for the full route template. Direct visits to a variant's own CMS URL (normal page route) should keep using `generatePage`.
168
+
169
+ ### 3. Create the Webhook Handler
170
+
171
+ ```typescript
172
+ // src/app/api/webhooks/ab-test/route.ts
173
+ import { createWebhookHandler } from '@se-studio/ab-testing/webhook';
174
+ import { getAbTestStore } from '@/server/abTestStore';
175
+ import { revalidateTag } from 'next/cache';
176
+
177
+ // Your Contentful fetch function
178
+ async function fetchPageTests() {
179
+ // Fetch all PageTest entries from Contentful
180
+ // Return array of RawPageTest objects
181
+ }
182
+
183
+ export const dynamic = 'force-dynamic';
184
+
185
+ export const POST = createWebhookHandler({
186
+ fetchPageTests,
187
+ getStore: getAbTestStore,
188
+ webhookSecret: process.env.CONTENTFUL_WEBHOOK_SECRET,
189
+ revalidate: () => revalidateTag('PageTests', { expire: 0 }),
190
+ });
191
+ ```
192
+
193
+ ### 4. Create Your Analytics Reporter (Project-Specific)
194
+
195
+ The package provides a `useAbTestAssignments` hook that returns test assignments for the current page. You create your own reporter component with your project's specific analytics integrations.
196
+
197
+ ```tsx
198
+ // src/components/AbTestReporter.tsx (project-specific)
199
+ 'use client';
200
+
201
+ import { useEffect } from 'react';
202
+ import { useAbTestAssignments } from '@se-studio/ab-testing';
203
+ import { sendEvent } from '@/lib/analytics';
204
+
205
+ export function AbTestReporter() {
206
+ const assignments = useAbTestAssignments();
207
+
208
+ useEffect(() => {
209
+ for (const assignment of assignments) {
210
+ // Send to GTM/GA4
211
+ sendEvent('experiment_impression', {
212
+ experiment_id: assignment.testId,
213
+ experiment_name: assignment.test_label,
214
+ variant_id: assignment.test_path,
215
+ });
216
+ }
217
+ }, [assignments]);
218
+
219
+ return null;
220
+ }
221
+ ```
222
+
223
+ **With HubSpot Integration (HSD-style):**
224
+
225
+ ```tsx
226
+ // src/components/AbTestReporter.tsx
227
+ 'use client';
228
+
229
+ import { useEffect } from 'react';
230
+ import { useAbTestAssignments } from '@se-studio/ab-testing';
231
+ import { sendEvent } from '@/lib/analytics';
232
+ import { sendHubspotCustomEvent } from '@/lib/hubspotCustomEvents';
233
+
234
+ export function AbTestReporter() {
235
+ const assignments = useAbTestAssignments();
236
+
237
+ useEffect(() => {
238
+ for (const assignment of assignments) {
239
+ // Send to GTM/GA4
240
+ sendEvent('experiment_impression', {
241
+ experiment_id: assignment.testId,
242
+ experiment_name: assignment.test_label,
243
+ variant_id: assignment.test_path,
244
+ });
245
+
246
+ // Send to HubSpot (if configured)
247
+ if (assignment.hubspot_event) {
248
+ sendHubspotCustomEvent(assignment.hubspot_event, {
249
+ experiment_name: assignment.test_label,
250
+ experiment_id: assignment.testId,
251
+ });
252
+ }
253
+ }
254
+ }, [assignments]);
255
+
256
+ return null;
257
+ }
258
+ ```
259
+
260
+ **Add to your layout:**
261
+
262
+ ```tsx
263
+ // src/app/layout.tsx
264
+ import { AbTestReporter } from '@/components/AbTestReporter';
265
+
266
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
267
+ return (
268
+ <html>
269
+ <body>
270
+ {children}
271
+ <AbTestReporter />
272
+ </body>
273
+ </html>
274
+ );
275
+ }
276
+ ```
277
+
278
+ ## Contentful Content Model
279
+
280
+ ### PageTest
281
+
282
+ | Field | Type | Description |
283
+ |-------|------|-------------|
284
+ | cmsLabel | Symbol | Internal label for the test |
285
+ | control | Reference (Page/PageVariant) | The control page to test against |
286
+ | enabled | Boolean | Whether the test is active |
287
+ | trackingLabel | Symbol (optional) | Override for analytics label |
288
+ | searchParameters | Symbol (optional) | URL params to match (e.g., "utm_source=google") |
289
+ | configuration | JSON | Array of `{weight, hubspot_event_name?}` |
290
+ | variants | References (PageVariant[]) | Variants to test |
291
+
292
+ ### PageVariant
293
+
294
+ Existing content type that references an original page and defines component swaps.
295
+
296
+ ## API Reference
297
+
298
+ ### Types
299
+
300
+ ```typescript
301
+ interface AbTest {
302
+ id: string;
303
+ cmsLabel: string;
304
+ controlSlug: string;
305
+ searchParameters?: string;
306
+ trackingLabel?: string;
307
+ enabled: boolean;
308
+ configuration: AbTestVariantConfig[];
309
+ variants: AbTestVariant[];
310
+ }
311
+
312
+ interface IBlobStore<T> {
313
+ get(key: string): Promise<T | undefined>;
314
+ set(key: string, value: T): Promise<void>;
315
+ bulkWrite(entries: [string, T][]): Promise<void>;
316
+ size(): Promise<number>;
317
+ values(): Promise<T[]>;
318
+ }
319
+
320
+ interface ActiveAbTestAssignment {
321
+ testId: string;
322
+ test_label: string;
323
+ test_path: string;
324
+ hubspot_event?: string;
325
+ original_path?: string;
326
+ }
327
+ ```
328
+
329
+ ### Middleware
330
+
331
+ ```typescript
332
+ import { createAbTestMiddleware } from '@se-studio/ab-testing/middleware';
333
+
334
+ const handler = createAbTestMiddleware({
335
+ getStore: () => store, // Required: blob store factory
336
+ cacheTtlMs: 60000, // Optional: cache TTL (default: 60s)
337
+ cookieName: 'ab-test-info', // Optional: cookie name
338
+ cookieMaxAge: 2592000, // Optional: cookie max age (default: 30 days)
339
+ shouldProcess: (path) => true, // Optional: filter requests
340
+ devTestData: [], // Optional: test data for development
341
+ });
342
+ ```
343
+
344
+ ### Webhook
345
+
346
+ ```typescript
347
+ import { createWebhookHandler } from '@se-studio/ab-testing/webhook';
348
+
349
+ export const POST = createWebhookHandler({
350
+ fetchPageTests: () => Promise<RawPageTest[]>, // Required
351
+ getStore: () => store, // Required
352
+ webhookSecret: 'secret', // Optional
353
+ revalidate: () => void, // Optional
354
+ onSkippedTest: (id, reason) => void, // Optional
355
+ });
356
+ ```
357
+
358
+ ### Hook
359
+
360
+ ```typescript
361
+ import { useAbTestAssignments } from '@se-studio/ab-testing';
362
+
363
+ // Returns array of assignments for the current page
364
+ const assignments = useAbTestAssignments({
365
+ cookieName: 'ab-test-info', // Optional: custom cookie name
366
+ });
367
+
368
+ // Each assignment contains:
369
+ // - testId: string
370
+ // - test_label: string
371
+ // - test_path: string (variant slug or "control")
372
+ // - hubspot_event?: string
373
+ // - original_path?: string
374
+ ```
375
+
376
+ ## Analytics Integration
377
+
378
+ ### Google Tag Manager (GTM)
379
+
380
+ Push events to `window.dataLayer`:
381
+
382
+ ```typescript
383
+ window.dataLayer.push({
384
+ event: 'experiment_impression',
385
+ experiment_id: assignment.testId,
386
+ experiment_name: assignment.test_label,
387
+ variant_id: assignment.test_path,
388
+ });
389
+ ```
390
+
391
+ ### Google Analytics 4 (GA4)
392
+
393
+ Register custom dimensions in GA4:
394
+ 1. Go to Admin > Data display > Custom definitions
395
+ 2. Create event-scoped dimensions for:
396
+ - `experiment_id`
397
+ - `experiment_name`
398
+ - `variant_id`
399
+
400
+ ## URL parameter injection
401
+
402
+ Page tests may define per-bucket `url_params` (or bare `utm_content` in configuration). Those values are stored on the assignment cookie for analytics.
403
+
404
+ **Address-bar injection is opt-in.** Both middleware and `AbTestUtmScript` apply `url_params` to the URL **only when** the test has `injectUrlParams: true` (stored on the cookie assignment as `injectUrlParams: true`). Default is **false**.
405
+
406
+ - Legacy cookies without `injectUrlParams` do **not** inject.
407
+ - Keep `<AbTestUtmScript />` in the root layout so opt-in tests still surface params for GTM before hydration.
408
+
409
+ ## License
410
+
411
+ MIT
412
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/codegen/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ /** biome-ignore-all lint/suspicious/noConsole: CLI script */
3
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { transformPageTest } from '../webhook/handler.js';
6
+ import { buildStaticTestsByPath, generateAbTestsFileContent } from './generator.js';
7
+ async function loadEnvFile(path) {
8
+ try {
9
+ const content = await readFile(path, 'utf-8');
10
+ for (const line of content.split('\n')) {
11
+ const trimmed = line.trim();
12
+ if (!trimmed || trimmed.startsWith('#'))
13
+ continue;
14
+ const eq = trimmed.indexOf('=');
15
+ if (eq === -1)
16
+ continue;
17
+ const key = trimmed.slice(0, eq).trim();
18
+ const val = trimmed
19
+ .slice(eq + 1)
20
+ .trim()
21
+ .replace(/^["']|["']$/g, '');
22
+ if (key && !(key in process.env))
23
+ process.env[key] = val;
24
+ }
25
+ }
26
+ catch {
27
+ // .env.local not present — rely on process.env
28
+ }
29
+ }
30
+ function getOutputPath() {
31
+ const outputArg = process.argv.find((a) => a.startsWith('--output='));
32
+ if (outputArg)
33
+ return outputArg.slice('--output='.length);
34
+ return process.env['AB_TESTS_OUTPUT'] ?? './src/generated/abTests.ts';
35
+ }
36
+ async function main() {
37
+ await loadEnvFile('.env.local');
38
+ const draftOnly = process.env['DRAFT_ONLY'] === 'true';
39
+ const spaceId = process.env['CONTENTFUL_SPACE_ID'];
40
+ const accessToken = draftOnly
41
+ ? process.env['CONTENTFUL_PREVIEW_ACCESS_TOKEN']
42
+ : process.env['CONTENTFUL_ACCESS_TOKEN'];
43
+ const environment = process.env['CONTENTFUL_ENVIRONMENT'] ?? process.env['CONTENTFUL_ENVIRONMENT_NAME'] ?? 'master';
44
+ if (!spaceId || !accessToken) {
45
+ console.error(draftOnly
46
+ ? 'Missing CONTENTFUL_SPACE_ID or CONTENTFUL_PREVIEW_ACCESS_TOKEN'
47
+ : 'Missing CONTENTFUL_SPACE_ID or CONTENTFUL_ACCESS_TOKEN');
48
+ process.exit(1);
49
+ }
50
+ const host = draftOnly ? 'preview.contentful.com' : 'cdn.contentful.com';
51
+ const url = `https://${host}/spaces/${spaceId}/environments/${environment}/entries?content_type=pageTest&include=1`;
52
+ console.log(`Fetching A/B tests from Contentful (environment: ${environment}, preview: ${String(draftOnly)})...`);
53
+ const response = await fetch(url, {
54
+ headers: { Authorization: `Bearer ${accessToken}` },
55
+ });
56
+ if (!response.ok) {
57
+ console.error(`Contentful API error: ${response.status} ${response.statusText}`);
58
+ process.exit(1);
59
+ }
60
+ const data = (await response.json());
61
+ const rawCount = data.items?.length ?? 0;
62
+ const includesCount = data.includes?.Entry?.length ?? 0;
63
+ console.log(` → ${rawCount} pageTest entry(s) returned, ${includesCount} linked entry(s) in includes`);
64
+ const includesMap = new Map();
65
+ for (const entry of data.includes?.Entry ?? []) {
66
+ includesMap.set(entry.sys.id, entry);
67
+ }
68
+ const rawTests = (data.items ?? []).map((item) => {
69
+ const { fields, sys } = item;
70
+ const controlRef = fields['control'];
71
+ const controlEntry = controlRef ? includesMap.get(controlRef.sys.id) : undefined;
72
+ if (controlRef && !controlEntry) {
73
+ console.warn(` → Test ${sys.id} ("${fields['cmsLabel']}"): control entry ${controlRef.sys.id} not found in includes (not published?)`);
74
+ }
75
+ const variantRefs = fields['variants'] ?? [];
76
+ const variantItems = variantRefs
77
+ .map((ref) => {
78
+ const entry = includesMap.get(ref.sys.id);
79
+ if (!entry) {
80
+ console.warn(` → Test ${sys.id}: variant entry ${ref.sys.id} not found in includes (not published?)`);
81
+ return null;
82
+ }
83
+ return { sys: ref.sys, slug: entry.fields['slug'] };
84
+ })
85
+ .filter((v) => v !== null);
86
+ console.log(` → Test "${fields['cmsLabel']}" (${sys.id}): enabled=${String(fields['enabled'])}, control slug="${controlEntry?.fields['slug'] ?? 'MISSING'}", ${variantItems.length} variant(s)`);
87
+ return {
88
+ sys,
89
+ cmsLabel: fields['cmsLabel'],
90
+ trackingLabel: fields['trackingLabel'],
91
+ enabled: fields['enabled'],
92
+ searchParameters: fields['searchParameters'],
93
+ injectUrlParams: fields['injectUrlParams'],
94
+ configuration: fields['configuration'],
95
+ control: controlRef && controlEntry
96
+ ? { sys: controlRef.sys, slug: controlEntry.fields['slug'] }
97
+ : null,
98
+ variantsCollection: { items: variantItems },
99
+ };
100
+ });
101
+ const tests = rawTests
102
+ .map((raw) => transformPageTest(raw, (id, reason) => console.warn(` → Skipping test ${id}: ${reason}`)))
103
+ .filter((t) => t !== null);
104
+ console.log(`Found ${tests.length} active test(s)`);
105
+ for (const test of tests) {
106
+ console.log(` → "${test.cmsLabel}" on /${test.controlSlug} with ${test.variants.length} variant(s)`);
107
+ }
108
+ const testsByPath = buildStaticTestsByPath(tests);
109
+ const pathCount = Object.keys(testsByPath).length;
110
+ console.log(`Mapped to ${pathCount} path(s): ${Object.keys(testsByPath).join(', ')}`);
111
+ const content = generateAbTestsFileContent(testsByPath);
112
+ const outputPath = resolve(getOutputPath());
113
+ await mkdir(dirname(outputPath), { recursive: true });
114
+ await writeFile(outputPath, content);
115
+ console.log(`Written to ${outputPath}`);
116
+ }
117
+ main().catch((error) => {
118
+ console.error('Failed to generate A/B test config:', error);
119
+ process.exit(1);
120
+ });
121
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/codegen/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { CachedAbTest } from '../middleware/types';
2
+ import type { AbTest } from '../types';
3
+ /**
4
+ * Build a plain-object testsByPath map suitable for static generation.
5
+ *
6
+ * This is the Record-based equivalent of the internal Map used at runtime.
7
+ * The result is JSON-serialisable and can be written to a generated TypeScript file
8
+ * with `generateAbTestsFileContent`.
9
+ *
10
+ * @param tests - Array of AbTest entries (e.g. from `transformPageTest`)
11
+ * @returns Record of normalised control path → CachedAbTest[]
12
+ */
13
+ export declare function buildStaticTestsByPath(tests: AbTest[]): Record<string, CachedAbTest[]>;
14
+ export interface GenerateAbTestsFileOptions {
15
+ /**
16
+ * The package to import `CachedAbTest` from in the generated file.
17
+ * @default "@se-studio/ab-testing"
18
+ */
19
+ importPath?: string;
20
+ }
21
+ /**
22
+ * Generate the content of a static TypeScript file exporting a `testsByPath` constant.
23
+ *
24
+ * Apps write this content to e.g. `src/lib/__generated/abTests.ts` at build time,
25
+ * then import `testsByPath` directly in their middleware — zero async overhead, no KV store needed.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // management/generateAbTests.ts
30
+ * import { writeFile } from 'node:fs/promises';
31
+ * import { transformPageTest } from '@se-studio/ab-testing/webhook';
32
+ * import { buildStaticTestsByPath, generateAbTestsFileContent } from '@se-studio/ab-testing/codegen';
33
+ * import { fetchRawPageTests } from '../src/cms/pageTests';
34
+ *
35
+ * const raw = await fetchRawPageTests();
36
+ * const tests = raw.map(r => transformPageTest(r)).filter(t => t !== null);
37
+ * const testsByPath = buildStaticTestsByPath(tests);
38
+ * await writeFile('./src/lib/__generated/abTests.ts', generateAbTestsFileContent(testsByPath));
39
+ * ```
40
+ */
41
+ export declare function generateAbTestsFileContent(testsByPath: Record<string, CachedAbTest[]>, options?: GenerateAbTestsFileOptions): string;
42
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/codegen/generator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAetF;AAED,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,EAC3C,OAAO,GAAE,0BAA+B,GACvC,MAAM,CAWR"}
@@ -0,0 +1,56 @@
1
+ import { computeCachedTest, normalizePath } from '../middleware/cache.js';
2
+ /**
3
+ * Build a plain-object testsByPath map suitable for static generation.
4
+ *
5
+ * This is the Record-based equivalent of the internal Map used at runtime.
6
+ * The result is JSON-serialisable and can be written to a generated TypeScript file
7
+ * with `generateAbTestsFileContent`.
8
+ *
9
+ * @param tests - Array of AbTest entries (e.g. from `transformPageTest`)
10
+ * @returns Record of normalised control path → CachedAbTest[]
11
+ */
12
+ export function buildStaticTestsByPath(tests) {
13
+ const result = {};
14
+ for (const test of tests) {
15
+ if (!test.enabled)
16
+ continue;
17
+ const path = normalizePath(test.controlSlug);
18
+ const cached = computeCachedTest(test);
19
+ const existing = result[path] ?? [];
20
+ existing.push(cached);
21
+ result[path] = existing;
22
+ }
23
+ return result;
24
+ }
25
+ /**
26
+ * Generate the content of a static TypeScript file exporting a `testsByPath` constant.
27
+ *
28
+ * Apps write this content to e.g. `src/lib/__generated/abTests.ts` at build time,
29
+ * then import `testsByPath` directly in their middleware — zero async overhead, no KV store needed.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // management/generateAbTests.ts
34
+ * import { writeFile } from 'node:fs/promises';
35
+ * import { transformPageTest } from '@se-studio/ab-testing/webhook';
36
+ * import { buildStaticTestsByPath, generateAbTestsFileContent } from '@se-studio/ab-testing/codegen';
37
+ * import { fetchRawPageTests } from '../src/cms/pageTests';
38
+ *
39
+ * const raw = await fetchRawPageTests();
40
+ * const tests = raw.map(r => transformPageTest(r)).filter(t => t !== null);
41
+ * const testsByPath = buildStaticTestsByPath(tests);
42
+ * await writeFile('./src/lib/__generated/abTests.ts', generateAbTestsFileContent(testsByPath));
43
+ * ```
44
+ */
45
+ export function generateAbTestsFileContent(testsByPath, options = {}) {
46
+ const { importPath = '@se-studio/ab-testing' } = options;
47
+ const json = JSON.stringify(testsByPath, null, 2);
48
+ return `// This file is automatically generated from Contentful CMS — do not edit manually.
49
+ // Re-generate: pnpm generate:ab-tests (runs automatically via predev/prebuild)
50
+
51
+ import type { CachedAbTest } from '${importPath}';
52
+
53
+ export const testsByPath: Record<string, CachedAbTest[]> = ${json};
54
+ `;
55
+ }
56
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/codegen/generator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAI1E;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAe;IACpD,MAAM,MAAM,GAAmC,EAAE,CAAC;IAElD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,SAAS;QAE5B,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAEvC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC;IAC1B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAUD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,0BAA0B,CACxC,WAA2C,EAC3C,UAAsC,EAAE;IAExC,MAAM,EAAE,UAAU,GAAG,uBAAuB,EAAE,GAAG,OAAO,CAAC;IACzD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAElD,OAAO;;;qCAG4B,UAAU;;6DAEc,IAAI;CAChE,CAAC;AACF,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @se-studio/ab-testing/codegen
3
+ *
4
+ * Build-time utilities for generating a static A/B test configuration file.
5
+ * Import these in a Node.js management script, not in edge/browser code.
6
+ */
7
+ export type { GenerateAbTestsFileOptions } from './generator';
8
+ export { buildStaticTestsByPath, generateAbTestsFileContent, } from './generator';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/codegen/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,aAAa,CAAC"}