@se-studio/ab-testing 1.0.73 → 1.0.74

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @se-studio/ab-testing
2
2
 
3
+ ## 1.0.74
4
+
5
+ ### Patch Changes
6
+
7
+ - Version bump: patch for changed packages
8
+
3
9
  ## 1.0.73
4
10
 
5
11
  ### Patch Changes
@@ -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,120 @@
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';
6
+ import { buildStaticTestsByPath, generateAbTestsFileContent } from './generator';
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
+ configuration: fields['configuration'],
94
+ control: controlRef && controlEntry
95
+ ? { sys: controlRef.sys, slug: controlEntry.fields['slug'] }
96
+ : null,
97
+ variantsCollection: { items: variantItems },
98
+ };
99
+ });
100
+ const tests = rawTests
101
+ .map((raw) => transformPageTest(raw, (id, reason) => console.warn(` → Skipping test ${id}: ${reason}`)))
102
+ .filter((t) => t !== null);
103
+ console.log(`Found ${tests.length} active test(s)`);
104
+ for (const test of tests) {
105
+ console.log(` → "${test.cmsLabel}" on /${test.controlSlug} with ${test.variants.length} variant(s)`);
106
+ }
107
+ const testsByPath = buildStaticTestsByPath(tests);
108
+ const pathCount = Object.keys(testsByPath).length;
109
+ console.log(`Mapped to ${pathCount} path(s): ${Object.keys(testsByPath).join(', ')}`);
110
+ const content = generateAbTestsFileContent(testsByPath);
111
+ const outputPath = resolve(getOutputPath());
112
+ await mkdir(dirname(outputPath), { recursive: true });
113
+ await writeFile(outputPath, content);
114
+ console.log(`Written to ${outputPath}`);
115
+ }
116
+ main().catch((error) => {
117
+ console.error('Failed to generate A/B test config:', error);
118
+ process.exit(1);
119
+ });
120
+ //# 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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"}
@@ -45,8 +45,8 @@ export function buildStaticTestsByPath(tests) {
45
45
  export function generateAbTestsFileContent(testsByPath, options = {}) {
46
46
  const { importPath = '@se-studio/ab-testing' } = options;
47
47
  const json = JSON.stringify(testsByPath, null, 2);
48
- return `// This file is automatically generated — do not edit manually.
49
- // Re-generate by running your management/generateAbTests script.
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
50
 
51
51
  import type { CachedAbTest } from '${importPath}';
52
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@se-studio/ab-testing",
3
- "version": "1.0.73",
3
+ "version": "1.0.74",
4
4
  "description": "Server-side A/B testing framework for Next.js applications with Contentful CMS",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,6 +37,9 @@
37
37
  "import": "./dist/components/index.js"
38
38
  }
39
39
  },
40
+ "bin": {
41
+ "generate-ab-tests": "./dist/codegen/cli.js"
42
+ },
40
43
  "files": [
41
44
  "dist",
42
45
  "*.md"
@@ -63,7 +66,7 @@
63
66
  "react": "^19.0.0"
64
67
  },
65
68
  "devDependencies": {
66
- "@biomejs/biome": "^2.4.12",
69
+ "@biomejs/biome": "^2.4.14",
67
70
  "@types/node": "^22.19.17",
68
71
  "@types/react": "^19.2.14",
69
72
  "next": "^15.5.15",