@forgecart/cli 2.202607190047.0 → 2.202607240817.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.
@@ -30,7 +30,7 @@ export interface InitOptions {
30
30
  * Serve-in-place mode: the target directory is ALREADY a scaffolded,
31
31
  * dependency- and dev-compile-warmed storefront (materialized into the pod's
32
32
  * `/workspace` from the image's pre-warmed tree). Write ONLY the per-channel
33
- * config + `.env.local` and skip the template copy entirely — copying would
33
+ * config + `.env` and skip the template copy entirely — copying would
34
34
  * clobber the warm `.next` and bust the Turbopack dev cache.
35
35
  */
36
36
  configOnly?: boolean;
@@ -40,7 +40,7 @@ export interface InitOptions {
40
40
  *
41
41
  * Resolves the target directory, scaffolds the storefront from the warmed
42
42
  * template when one is available (see {@link resolveScaffoldPlan}) or the
43
- * bundled template otherwise, writes the ForgeCart config and `.env.local`, and
43
+ * bundled template otherwise, writes the ForgeCart config and `.env`, and
44
44
  * prints next steps. Throws a clear Error on any failure (missing token,
45
45
  * missing template, copy failure).
46
46
  */
@@ -123,8 +123,8 @@ function buildConfig(channelToken, options) {
123
123
  storefrontPath: '.',
124
124
  };
125
125
  }
126
- /** Build the `.env.local` contents for the storefront SDK client. */
127
- function buildEnvLocal(channelToken, apiUrl) {
126
+ /** Build the `.env` contents for the storefront SDK client. */
127
+ function buildEnv(channelToken, apiUrl) {
128
128
  return [
129
129
  `FORGECART_CHANNEL_TOKEN=${channelToken}`,
130
130
  `FORGECART_SHOP_API_URL=${apiUrl}`,
@@ -133,7 +133,7 @@ function buildEnvLocal(channelToken, apiUrl) {
133
133
  }
134
134
  /**
135
135
  * Write the per-channel config the storefront reads at runtime: `.forgecart/
136
- * config.json` (channel token, api url, app flag) and `.env.local` (the SDK
136
+ * config.json` (channel token, api url, app flag) and `.env` (the SDK
137
137
  * client env). Shared by the full scaffold and the `--config-only` serve-in-place
138
138
  * path, so both write byte-identical config — the only difference is whether the
139
139
  * template tree was copied first.
@@ -142,7 +142,7 @@ async function writeChannelConfig(targetDir, channelToken, options) {
142
142
  const configDir = join(targetDir, '.forgecart');
143
143
  await mkdir(configDir, { recursive: true });
144
144
  await writeFile(join(configDir, 'config.json'), `${JSON.stringify(buildConfig(channelToken, options), null, 2)}\n`, 'utf8');
145
- await writeFile(join(targetDir, '.env.local'), buildEnvLocal(channelToken, options.apiUrl), 'utf8');
145
+ await writeFile(join(targetDir, '.env'), buildEnv(channelToken, options.apiUrl), 'utf8');
146
146
  }
147
147
  /** Print the post-scaffold next steps. */
148
148
  function printNextSteps(targetDir, application, depsInstalled) {
@@ -179,7 +179,7 @@ function printConfigOnly(targetDir) {
179
179
  *
180
180
  * Resolves the target directory, scaffolds the storefront from the warmed
181
181
  * template when one is available (see {@link resolveScaffoldPlan}) or the
182
- * bundled template otherwise, writes the ForgeCart config and `.env.local`, and
182
+ * bundled template otherwise, writes the ForgeCart config and `.env`, and
183
183
  * prints next steps. Throws a clear Error on any failure (missing token,
184
184
  * missing template, copy failure).
185
185
  */
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,0DAA0D;AAC1D,MAAM,CAAC,MAAM,oBAAoB,GAAG,2BAA2B,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,wBAAwB,CAAC;AAEnE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,4BAA4B,CAAC;AAmDnE;;;;;GAKG;AACH,SAAS,wBAAwB;IAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;AACpE,CAAC;AAED,kDAAkD;AAClD,KAAK,UAAU,WAAW,CAAC,IAAY;IACrC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,qDAAqD;AACrD,KAAK,UAAU,MAAM,CAAC,IAAY;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,sBAAsB,CAAC,YAAoB;IACxD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,mBAAmB,CAAC,kBAA0B;IAC3D,MAAM,OAAO,GAAiB,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;IAErF,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IAC1D,IAAI,CAAC,SAAS;QAAE,OAAO,OAAO,CAAC;IAC/B,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,SAAS,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IAEpD,MAAM,oBAAoB,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAC7D,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,oBAAoB,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IAE/D,MAAM,cAAc,GAAG,MAAM,sBAAsB,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;IACrF,MAAM,eAAe,GAAG,MAAM,sBAAsB,CAClD,IAAI,CAAC,kBAAkB,EAAE,cAAc,CAAC,CACzC,CAAC;IACF,IAAI,CAAC,cAAc,IAAI,CAAC,eAAe,IAAI,cAAc,KAAK,eAAe,EAAE,CAAC;QAC9E,OAAO;YACL,MAAM,EAAE,6BAA6B;YACrC,WAAW,EAAE,kBAAkB;YAC/B,oBAAoB;SACrB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,oBAAoB,CAAC,SAAiB,EAAE,IAAkB;IACvE,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IAC3D,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,EAAE,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG;QACb,UAAU,EAAE,IAAI,CAAC,WAAW;QAC5B,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACnC,CAAC;IACF,MAAM,SAAS,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC9E,CAAC;AAED,kDAAkD;AAClD,SAAS,WAAW,CAAC,YAAoB,EAAE,OAAoB;IAC7D,OAAO;QACL,OAAO,EAAE,KAAK;QACd,YAAY;QACZ,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,cAAc,EAAE,OAAO,CAAC,WAAW,IAAI,KAAK;QAC5C,cAAc,EAAE,GAAG;KACpB,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,SAAS,QAAQ,CAAC,YAAoB,EAAE,MAAc;IACpD,OAAO;QACL,2BAA2B,YAAY,EAAE;QACzC,0BAA0B,MAAM,EAAE;QAClC,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,kBAAkB,CAC/B,SAAiB,EACjB,YAAoB,EACpB,OAAoB;IAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAChD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,SAAS,CACb,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,EAC9B,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAClE,MAAM,CACP,CAAC;IACF,MAAM,SAAS,CACb,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,EACvB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,EACtC,MAAM,CACP,CAAC;AACJ,CAAC;AAED,0CAA0C;AAC1C,SAAS,cAAc,CAAC,SAAiB,EAAE,WAAoB,EAAE,aAAsB;IACrF,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,KAAK,CACH,aAAa;QACX,CAAC,CAAC,2FAA2F;QAC7F,CAAC,CAAC,qCAAqC,CAC1C,CAAC;IACF,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,KAAK,CAAC,aAAa,CAAC,CAAC;IACrB,KAAK,CAAC,QAAQ,SAAS,EAAE,CAAC,CAAC;IAC3B,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,KAAK,CAAC,eAAe,CAAC,CAAC;IACzB,CAAC;IACD,KAAK,CAAC,eAAe,CAAC,CAAC;IACvB,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,IAAI,WAAW,EAAE,CAAC;QAChB,KAAK,CACH,2EAA2E;YACzE,0BAA0B,CAC7B,CAAC;QACF,KAAK,CAAC,EAAE,CAAC,CAAC;IACZ,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,SAAS,eAAe,CAAC,SAAiB;IACxC,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,KAAK,CAAC,kFAAkF,CAAC,CAAC;IAC1F,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,KAAK,CAAC,aAAa,CAAC,CAAC;IACrB,KAAK,CAAC,QAAQ,SAAS,EAAE,CAAC,CAAC;IAC3B,KAAK,CAAC,eAAe,CAAC,CAAC;IACvB,KAAK,CAAC,EAAE,CAAC,CAAC;AACZ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAwB,EAAE,OAAoB;IAC1E,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAEtE,gFAAgF;IAChF,0EAA0E;IAC1E,+EAA+E;IAC/E,kFAAkF;IAClF,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,KAAK,CACb,oDAAoD,SAAS,2BAA2B,CACzF,CAAC;QACJ,CAAC;QACD,MAAM,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;QAC3D,eAAe,CAAC,SAAS,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,wBAAwB,EAAE,CAAC;IAC/C,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,oCAAoC,WAAW,GAAG,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,IAAI,IAAI,CAAC,MAAM,KAAK,6BAA6B,EAAE,CAAC;QAClD,MAAM,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE;YACnE,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAC3D,MAAM,oBAAoB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAE5C,cAAc,CAAC,IAAI,IAAI,GAAG,EAAE,OAAO,CAAC,WAAW,IAAI,KAAK,EAAE,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC;AACtF,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;SACvB,WAAW,CAAC,yDAAyD,CAAC;SACtE,QAAQ,CAAC,QAAQ,EAAE,2DAA2D,CAAC;SAC/E,MAAM,CAAC,qBAAqB,EAAE,oCAAoC,CAAC;SACnE,MAAM,CACL,iBAAiB,EACjB,yDAAyD,EACzD,oBAAoB,CACrB;SACA,MAAM,CAAC,mBAAmB,EAAE,uDAAuD,CAAC;SACpF,MAAM,CACL,eAAe,EACf,+FAA+F,CAChG;SACA,MAAM,CAAC,KAAK,EAAE,IAAwB,EAAE,OAAoB,EAAE,EAAE;QAC/D,MAAM,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACP,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgecart/cli",
3
- "version": "2.202607190047.0",
3
+ "version": "2.202607240817.0",
4
4
  "type": "module",
5
5
  "description": "CLI for scaffolding and operating ForgeCart channel storefronts",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  # ForgeCart channel credentials.
2
2
  #
3
- # `forgecart init` writes the real values to `.env.local` for you. This file
3
+ # `forgecart init` writes the real values to `.env` for you. This file
4
4
  # documents the variables the storefront reads. Both are required and used
5
5
  # server-side only (never exposed to the browser).
6
6
 
@@ -17,8 +17,8 @@ The storefront reads two environment variables (server-side only):
17
17
  | `FORGECART_CHANNEL_TOKEN` | Identifies the channel; sent as the `forgecart-token` header. |
18
18
  | `FORGECART_SHOP_API_URL` | Base URL of the channel's shop GraphQL API (`.../shop-api`). |
19
19
 
20
- `forgecart init` writes both to `.env.local` for you. To configure manually,
21
- copy `.env.example` to `.env.local` and fill them in.
20
+ `forgecart init` writes both to `.env` for you. To configure manually,
21
+ copy `.env.example` to `.env` and fill them in.
22
22
 
23
23
  These values are consumed in [`src/lib/forgecart.ts`](src/lib/forgecart.ts),
24
24
  which constructs and memoizes a singleton `ForgeCartShopClient`:
@@ -4,22 +4,23 @@ const nextConfig = {
4
4
  // storefront with `next start` without a full node_modules tree.
5
5
  output: 'standalone',
6
6
  reactStrictMode: true,
7
- // The visual-editor preview runs THIS dev server inside a workspace pod and is
8
- // reached cross-origin through the channel's preview ingress host
9
- // (`<code>.127.0.0.1.nip.io:8081` on a bare host; `<code>-<vm>.vm.forgecart.com`
10
- // through the Cloudflare tunnel of a `pnpm vm` environment; `<code>.dev|test
11
- // .forgecart.dev` and `<code>.forgecart.dev` deployed)never the pod's own
12
- // `localhost:3000`. Next 16 ENFORCES `allowedDevOrigins`: a dev request whose
13
- // host is not localhost/127.0.0.1 and not listed here is blocked, which
14
- // silently kills the `/_next` HMR websocket (and the dashboard iframe's dev
15
- // channel) with no page error. The `*` wildcard matches across dots, so
16
- // `*.forgecart.dev` covers every env's preview subdomain. Dev-only —
17
- // `next start` (deployed storefronts) ignores it.
7
+ // The visual-editor preview runs THIS dev server inside a workspace pod,
8
+ // embedded cross-origin in the dashboard's editor iframe. Next 16 ENFORCES
9
+ // `allowedDevOrigins`: it 403s any cross-site request to an internal dev
10
+ // resource (`/_next`, `/__nextjs`, the HMR websocket) whose Origin/Referer
11
+ // host is not localhost/127.0.0.1 and not listed here silently killing the
12
+ // dashboard iframe's dev channel with no page error. So this list must name
13
+ // every ORIGIN THAT EMBEDS the editor — i.e. each env's DASHBOARD host not
14
+ // just the preview subdomain. GOTCHA: `*` matches exactly ONE label (Next's
15
+ // `matchWildcardDomain`), so `*.forgecart.dev` does NOT cover the two-label
16
+ // prod preview host `<code>.preview.forgecart.dev` it must be listed
17
+ // explicitly. Dev-only — `next start` (deployed storefronts) ignores it.
18
18
  allowedDevOrigins: [
19
- '*.127.0.0.1.nip.io',
20
- '*.vm.forgecart.com',
21
- '*.dev.forgecart.dev',
22
- '*.forgecart.dev',
19
+ '*.vm.forgecart.com', // `pnpm vm` Cloudflare-tunnel dashboards
20
+ '*.dev.forgecart.dev', // dev/test dashboard + preview subdomains
21
+ '*.forgecart.dev', // single-label deployed preview hosts
22
+ '*.preview.forgecart.dev', // two-label prod preview host (`*.forgecart.dev` can't match it)
23
+ 'dashboard.forgecart.com', // prod dashboard origin that embeds the editor
23
24
  ],
24
25
  experimental: {
25
26
  // Persist Turbopack's dev compile artifacts to `.next` so a pod can serve
@@ -8,11 +8,12 @@
8
8
  "start": "next start"
9
9
  },
10
10
  "dependencies": {
11
- "@forgecart/designer-runtime": "^1.0.0",
11
+ "@forgecart/designer-runtime": "^1.202607161748.0",
12
12
  "@forgecart/sdk": "^1.2.6",
13
13
  "next": "^16.1.0",
14
14
  "react": "^19.0.0",
15
- "react-dom": "^19.0.0"
15
+ "react-dom": "^19.0.0",
16
+ "uuidv7": "^1.1.0"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/node": "^22.10.0",
@@ -0,0 +1,168 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { NextRequest } from 'next/server';
3
+
4
+ import {
5
+ forwardTrackEvent,
6
+ getUpstreamConfig,
7
+ isObviousBot,
8
+ type TrackEventInput,
9
+ } from '../../../lib/track-forward';
10
+
11
+ /**
12
+ * Same-origin marketing-event relay (design doc S7).
13
+ *
14
+ * `ForgeTracker` batches client events (page_view, cta_click, heartbeat, …)
15
+ * and POSTs them here; this handler forwards each item to the ForgeCart shop
16
+ * API as a `trackEvent` GraphQL mutation. Unlike `__forge_beacon` this route
17
+ * is a PRODUCTION feature — tracking runs on deployed storefronts — with one
18
+ * inert guard: before `forgecart init` writes `.env` (the image-build
19
+ * prewarm) there is no shop to talk to, so every item answers `accepted:false`
20
+ * without any upstream call and the server never crashes.
21
+ *
22
+ * The upstream hop itself (raw HTTP GraphQL POST with the channel token, the
23
+ * shopper session as Bearer, UA + `x-forwarded-for` enrichment, and the
24
+ * why-not-the-SDK rationale) lives in `src/lib/track-forward.ts` — shared with
25
+ * the F-B `experiment_exposure` emission so both server-side emitters speak
26
+ * one contract. This route owns everything batch-shaped: parsing, per-item
27
+ * validation, the bot gate, session threading, and cookie persistence.
28
+ *
29
+ * Session capture mirrors `cart-actions.ts#persistSession`: the shop API
30
+ * surfaces a freshly minted session in the response `extensions`
31
+ * (`forgecart-auth-token`) exactly once; the first touched item captures it,
32
+ * the remaining items of the batch ride it as Bearer (one identity per batch),
33
+ * and the relay response persists it into the httpOnly `forgecart-session`
34
+ * cookie with the same attributes the cart actions use — the first tracked
35
+ * interaction establishes the shopper identity the cart will later reuse.
36
+ *
37
+ * Contract: per-item isolation (one upstream failure never kills the batch),
38
+ * `accepted:false` is TERMINAL for that item (the tracker never retries), and
39
+ * obviously non-human user agents short-circuit the whole batch with zero
40
+ * upstream calls.
41
+ *
42
+ * Folder name: `%5F%5Ffc` is URL-encoded `__fc` — the App Router treats
43
+ * `_`-prefixed folders as PRIVATE (excluded from routing), and the `%5F`
44
+ * escape is Next's documented way to serve a literal-underscore URL segment.
45
+ * A folder literally named `__fc` would silently 404.
46
+ */
47
+ export const dynamic = 'force-dynamic';
48
+
49
+ const SESSION_COOKIE = 'forgecart-session';
50
+ /** Mirrors `cart-actions.ts#persistSession` — the cookie is one shared identity. */
51
+ const SESSION_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
52
+
53
+ /** The tracker flushes at ≤10 events; anything past this is not our client. */
54
+ const MAX_BATCH_SIZE = 50;
55
+
56
+ /** Canonical textual UUID — the only client `eventId` shape the API accepts. */
57
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
58
+
59
+ interface TrackItemResult {
60
+ accepted: boolean;
61
+ eventId: string | null;
62
+ }
63
+
64
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
65
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
66
+ }
67
+
68
+ /** Parse the request body into a bounded batch array, or `null` when malformed. */
69
+ async function parseBatch(request: NextRequest): Promise<unknown[] | null> {
70
+ let body: unknown;
71
+ try {
72
+ body = await request.json();
73
+ } catch {
74
+ return null;
75
+ }
76
+ if (!isPlainObject(body) || !Array.isArray(body.events)) return null;
77
+ if (body.events.length === 0 || body.events.length > MAX_BATCH_SIZE) return null;
78
+ return body.events;
79
+ }
80
+
81
+ /** Copy an optional string field from the raw item, rejecting non-string junk. */
82
+ function readOptionalString(value: unknown): string | undefined {
83
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
84
+ }
85
+
86
+ /**
87
+ * Validate one raw batch item into a `TrackEventInput`, or `null` when the
88
+ * item is malformed (missing/empty `eventType`, non-UUID `eventId`, non-object
89
+ * `properties`). Only allowlisted fields cross to the upstream call.
90
+ */
91
+ function validateItem(raw: unknown): TrackEventInput | null {
92
+ if (!isPlainObject(raw)) return null;
93
+ const { eventType, eventId, properties } = raw;
94
+ if (typeof eventType !== 'string' || eventType.length === 0) return null;
95
+ if (eventId !== undefined && (typeof eventId !== 'string' || !UUID_PATTERN.test(eventId))) {
96
+ return null;
97
+ }
98
+ if (properties !== undefined && !isPlainObject(properties)) return null;
99
+
100
+ const input: TrackEventInput = { eventType };
101
+ if (typeof eventId === 'string') input.eventId = eventId;
102
+ const occurredAt = readOptionalString(raw.occurredAt);
103
+ if (occurredAt) input.occurredAt = occurredAt;
104
+ if (isPlainObject(properties)) input.properties = properties;
105
+ const utmSource = readOptionalString(raw.utmSource);
106
+ if (utmSource) input.utmSource = utmSource;
107
+ const utmMedium = readOptionalString(raw.utmMedium);
108
+ if (utmMedium) input.utmMedium = utmMedium;
109
+ const utmCampaign = readOptionalString(raw.utmCampaign);
110
+ if (utmCampaign) input.utmCampaign = utmCampaign;
111
+ const utmTerm = readOptionalString(raw.utmTerm);
112
+ if (utmTerm) input.utmTerm = utmTerm;
113
+ const utmContent = readOptionalString(raw.utmContent);
114
+ if (utmContent) input.utmContent = utmContent;
115
+ const currencyCode = readOptionalString(raw.currencyCode);
116
+ if (currencyCode) input.currencyCode = currencyCode;
117
+ return input;
118
+ }
119
+
120
+ export async function POST(request: NextRequest): Promise<NextResponse> {
121
+ const batch = await parseBatch(request);
122
+ if (!batch) {
123
+ return NextResponse.json({ error: 'malformed batch' }, { status: 400 });
124
+ }
125
+
126
+ const rejectAll = (): NextResponse =>
127
+ NextResponse.json({
128
+ results: batch.map((): TrackItemResult => ({ accepted: false, eventId: null })),
129
+ });
130
+
131
+ // Inert before `forgecart init` writes `.env` (image-prewarm contract).
132
+ if (!getUpstreamConfig()) return rejectAll();
133
+
134
+ const userAgent = request.headers.get('user-agent') ?? '';
135
+ if (isObviousBot(userAgent)) return rejectAll();
136
+
137
+ const incomingSession = request.cookies.get(SESSION_COOKIE)?.value ?? null;
138
+ const forwardedFor = request.headers.get('x-forwarded-for');
139
+
140
+ let sessionToken = incomingSession;
141
+ const results: TrackItemResult[] = [];
142
+ // Sequential on purpose: the first touched item mints the session and every
143
+ // later item of the batch must ride it — parallel sends would mint one
144
+ // identity per item and shatter attribution.
145
+ for (const raw of batch) {
146
+ const input = validateItem(raw);
147
+ if (!input) {
148
+ results.push({ accepted: false, eventId: null });
149
+ continue;
150
+ }
151
+ const outcome = await forwardTrackEvent(input, { sessionToken, userAgent, forwardedFor });
152
+ results.push({ accepted: outcome.accepted, eventId: outcome.eventId });
153
+ if (outcome.sessionToken) sessionToken = outcome.sessionToken;
154
+ }
155
+
156
+ const response = NextResponse.json({ results });
157
+ // Persist a newly minted (or upstream-refreshed) session exactly like
158
+ // `cart-actions.ts#persistSession` — same cookie, same attributes.
159
+ if (sessionToken && sessionToken !== incomingSession) {
160
+ response.cookies.set(SESSION_COOKIE, sessionToken, {
161
+ httpOnly: true,
162
+ sameSite: 'lax',
163
+ path: '/',
164
+ maxAge: SESSION_COOKIE_MAX_AGE_SECONDS,
165
+ });
166
+ }
167
+ return response;
168
+ }
@@ -1,8 +1,8 @@
1
1
  import type { Metadata } from 'next';
2
2
  import type { ReactNode } from 'react';
3
3
 
4
- import { ForgeAnalytics } from '../components/ForgeAnalytics';
5
4
  import { ForgeErrorBeacon } from '../components/ForgeErrorBeacon';
5
+ import { ForgeTracker } from '../components/ForgeTracker';
6
6
  import { ForgecartDesigner } from '../components/ForgecartDesigner';
7
7
  import { Header } from '../components/Header';
8
8
  import { getCart } from '../lib/cart-actions';
@@ -31,7 +31,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
31
31
  </CartProvider>
32
32
  <ForgecartDesigner />
33
33
  <ForgeErrorBeacon />
34
- <ForgeAnalytics
34
+ <ForgeTracker
35
35
  shopApiUrl={process.env.FORGECART_SHOP_API_URL ?? ''}
36
36
  channelToken={process.env.FORGECART_CHANNEL_TOKEN ?? ''}
37
37
  />
@@ -1,6 +1,7 @@
1
1
  import Link from 'next/link';
2
2
 
3
3
  import { ProductCard } from '../components/ProductCard';
4
+ import { getVariant } from '../lib/experiments';
4
5
  import { getFeaturedProducts } from '../lib/forgecart';
5
6
 
6
7
  // Product data changes in the dashboard, so render on each request.
@@ -8,16 +9,27 @@ export const dynamic = 'force-dynamic';
8
9
 
9
10
  export default async function HomePage() {
10
11
  const products = await getFeaturedProducts(4);
12
+ // A/B example (design doc F-B): variants are code branches keyed on
13
+ // `variantKey`. `hero-headline` is the template's reference experiment —
14
+ // create one with that key (variants `control` + `b`) in the dashboard and
15
+ // this hero starts splitting; until then `variantKey` is null and the
16
+ // default copy renders. `data-fc-variant` mirrors the assignment for
17
+ // debugging/verification; exposure tracking happens server-side.
18
+ const hero = await getVariant('hero-headline');
11
19
 
12
20
  return (
13
21
  <div className="space-y-12">
14
- <section className="rounded-2xl bg-white p-10 shadow-sm">
22
+ <section
23
+ data-fc-variant={hero.variantKey ?? 'default'}
24
+ className="rounded-2xl bg-white p-10 shadow-sm"
25
+ >
15
26
  <h1 className="text-3xl font-bold tracking-tight text-gray-900">
16
27
  Welcome to the shop
17
28
  </h1>
18
29
  <p className="mt-3 max-w-2xl text-gray-600">
19
- This storefront is served by a ForgeCart channel. Browse the catalog
20
- and add products to your cart.
30
+ {hero.variantKey === 'b'
31
+ ? 'Fresh picks, fair prices — see what everyone is adding to their carts.'
32
+ : 'This storefront is served by a ForgeCart channel. Browse the catalog and add products to your cart.'}
21
33
  </p>
22
34
  <Link
23
35
  href="/products"
@@ -33,6 +33,7 @@ export function CartView({ channelGroups }: { channelGroups: SellingPlanGroup[]
33
33
  <p className="text-gray-500">Your cart is empty.</p>
34
34
  <Link
35
35
  href="/products"
36
+ data-fc-track="cart-browse-products"
36
37
  className="inline-block rounded-md bg-gray-900 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-gray-700"
37
38
  >
38
39
  Browse products
@@ -134,6 +135,7 @@ export function CartView({ channelGroups }: { channelGroups: SellingPlanGroup[]
134
135
  type="button"
135
136
  onClick={() => setSellingPlan(plan.id)}
136
137
  disabled={pending}
138
+ data-fc-track="cart-subscribe-order"
137
139
  aria-pressed={active}
138
140
  className={`rounded-md border px-3 py-2 text-left text-sm transition disabled:opacity-50 ${
139
141
  active