@decocms/blocks 7.64.3 → 7.65.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks",
3
- "version": "7.64.3",
3
+ "version": "7.65.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -65,6 +65,7 @@
65
65
  "./sdk/invoke": "./src/sdk/invoke.ts",
66
66
  "./sdk/logger": "./src/sdk/logger.ts",
67
67
  "./sdk/mergeCacheControl": "./src/sdk/mergeCacheControl.ts",
68
+ "./sdk/nonce": "./src/sdk/nonce.ts",
68
69
  "./sdk/normalizeUrls": "./src/sdk/normalizeUrls.ts",
69
70
  "./sdk/observability": "./src/sdk/observability.ts",
70
71
  "./sdk/otel": "./src/sdk/otel.ts",
@@ -32,11 +32,11 @@ describe("parseDraftPointer", () => {
32
32
  it("parses <authority><path>@<version>, lowercasing the authority", () => {
33
33
  expect(
34
34
  parseDraftPointer(
35
- "Studio.decocms.com/api/fila/decofile/vm-1/main?token=Tok.abc@8c1d44e",
35
+ "Studio.decocms.com/api/acme/decofile/vm-1/main?token=Tok.abc@8c1d44e",
36
36
  ),
37
37
  ).toEqual({
38
38
  host: "studio.decocms.com",
39
- path: "/api/fila/decofile/vm-1/main?token=Tok.abc",
39
+ path: "/api/acme/decofile/vm-1/main?token=Tok.abc",
40
40
  version: "8c1d44e",
41
41
  });
42
42
  });
@@ -138,18 +138,18 @@ describe("gating", () => {
138
138
  });
139
139
 
140
140
  it("matches request hosts verbatim, port included, case-insensitively", () => {
141
- const env = { DECO_ALLOWED_PREVIEW_HOSTS: "fila.vtex.app, localhost:3100" };
142
- expect(isDraftHostAllowed("FILA.VTEX.APP", env)).toBe(true);
141
+ const env = { DECO_ALLOWED_PREVIEW_HOSTS: "acme.vtex.app, localhost:3100" };
142
+ expect(isDraftHostAllowed("ACME.VTEX.APP", env)).toBe(true);
143
143
  expect(isDraftHostAllowed("localhost:3100", env)).toBe(true);
144
- expect(isDraftHostAllowed("fila.com.br", env)).toBe(false);
144
+ expect(isDraftHostAllowed("acme.com.br", env)).toBe(false);
145
145
  expect(isDraftHostAllowed("localhost", env)).toBe(false);
146
146
  expect(isDraftHostAllowed(null, env)).toBe(false);
147
- expect(isDraftHostAllowed("fila.vtex.app", {})).toBe(false);
147
+ expect(isDraftHostAllowed("acme.vtex.app", {})).toBe(false);
148
148
  });
149
149
  });
150
150
 
151
151
  // Canonical Studio decofile-API pointer prefix (authority + path, no version).
152
- const P = "studio.decocms.com/api/fila/decofile/vm-1/main?token=tok.abc";
152
+ const P = "studio.decocms.com/api/acme/decofile/vm-1/main?token=tok.abc";
153
153
 
154
154
  describe("resolveDraftDecofile", () => {
155
155
  it("fetches the token's path on its validated origin", async () => {
@@ -168,7 +168,7 @@ describe("resolveDraftDecofile", () => {
168
168
  // content-addressed so Studio can answer with CDN-cacheable immutable
169
169
  // headers when it matches the served sha.
170
170
  expect(calls).toEqual([
171
- "https://studio.decocms.com/api/fila/decofile/vm-1/main?token=tok.abc&v=v1",
171
+ "https://studio.decocms.com/api/acme/decofile/vm-1/main?token=tok.abc&v=v1",
172
172
  ]);
173
173
  });
174
174
 
@@ -298,10 +298,10 @@ describe("DEFAULT_PREVIEW_API_DOMAINS", () => {
298
298
 
299
299
  describe("site-block preview hosts", () => {
300
300
  it("enables the feature from the site block alone — no env needed", () => {
301
- setDraftPreviewHosts(["fila.vtex.app", "LOCALHOST:3100", 42, " "]);
301
+ setDraftPreviewHosts(["acme.vtex.app", "LOCALHOST:3100", 42, " "]);
302
302
  try {
303
303
  expect(isDraftPreviewEnabled({})).toBe(true);
304
- expect(isDraftHostAllowed("fila.vtex.app", {})).toBe(true);
304
+ expect(isDraftHostAllowed("acme.vtex.app", {})).toBe(true);
305
305
  // Sanitized: lowercased, non-strings and blanks dropped.
306
306
  expect(isDraftHostAllowed("localhost:3100", {})).toBe(true);
307
307
  expect(isDraftHostAllowed("evil.example", {})).toBe(false);
@@ -311,13 +311,13 @@ describe("site-block preview hosts", () => {
311
311
  });
312
312
 
313
313
  it("env REPLACES the block hosts when set — the operational escape hatch", () => {
314
- setDraftPreviewHosts(["fila.vtex.app"]);
314
+ setDraftPreviewHosts(["acme.vtex.app"]);
315
315
  try {
316
316
  const env = { DECO_ALLOWED_PREVIEW_HOSTS: "other.example" };
317
317
  expect(isDraftHostAllowed("other.example", env)).toBe(true);
318
318
  // Not merged: env is a kill switch / override, so the block value must
319
319
  // not survive alongside it.
320
- expect(isDraftHostAllowed("fila.vtex.app", env)).toBe(false);
320
+ expect(isDraftHostAllowed("acme.vtex.app", env)).toBe(false);
321
321
  } finally {
322
322
  setDraftPreviewHosts([]);
323
323
  }
@@ -326,41 +326,41 @@ describe("site-block preview hosts", () => {
326
326
 
327
327
  describe("deco-hosted preview domains (setDecoSiteHost)", () => {
328
328
  it("infers <site>.deco.site and enables the feature", () => {
329
- setDecoSiteHost("als-storefront");
329
+ setDecoSiteHost("acme");
330
330
  try {
331
331
  expect(isDraftPreviewEnabled({})).toBe(true);
332
- expect(isDraftHostAllowed("als-storefront.deco.site", {})).toBe(true);
332
+ expect(isDraftHostAllowed("acme.deco.site", {})).toBe(true);
333
333
  expect(isDraftHostAllowed("other.deco.site", {})).toBe(false);
334
334
  // A custom production domain is never inferred.
335
- expect(isDraftHostAllowed("www.als-storefront.com", {})).toBe(false);
335
+ expect(isDraftHostAllowed("www.acme.com", {})).toBe(false);
336
336
  } finally {
337
337
  setDecoSiteHost(null);
338
338
  }
339
339
  });
340
340
 
341
341
  it("infers the <site>.deco-cx.workers.dev deploy host", () => {
342
- setDecoSiteHost("casaevideo-tanstack");
342
+ setDecoSiteHost("acme-tanstack");
343
343
  try {
344
- expect(isDraftHostAllowed("casaevideo-tanstack.deco-cx.workers.dev", {})).toBe(true);
344
+ expect(isDraftHostAllowed("acme-tanstack.deco-cx.workers.dev", {})).toBe(true);
345
345
  // Exact match only — another worker on the same account is not admitted.
346
346
  expect(isDraftHostAllowed("other-site.deco-cx.workers.dev", {})).toBe(false);
347
347
  // No nested subdomain widens the match.
348
348
  expect(
349
- isDraftHostAllowed("casaevideo-tanstack.evil.deco-cx.workers.dev", {}),
349
+ isDraftHostAllowed("acme-tanstack.evil.deco-cx.workers.dev", {}),
350
350
  ).toBe(false);
351
351
  // Wrong apex.
352
- expect(isDraftHostAllowed("casaevideo-tanstack.deco-cx.workers.example", {})).toBe(false);
352
+ expect(isDraftHostAllowed("acme-tanstack.deco-cx.workers.example", {})).toBe(false);
353
353
  } finally {
354
354
  setDecoSiteHost(null);
355
355
  }
356
356
  });
357
357
 
358
358
  it("is merged ON TOP of the site block, not replacing it", () => {
359
- setDraftPreviewHosts(["fila.vtex.app"]);
360
- setDecoSiteHost("als-storefront");
359
+ setDraftPreviewHosts(["acme.vtex.app"]);
360
+ setDecoSiteHost("acme");
361
361
  try {
362
- expect(isDraftHostAllowed("fila.vtex.app", {})).toBe(true);
363
- expect(isDraftHostAllowed("als-storefront.deco.site", {})).toBe(true);
362
+ expect(isDraftHostAllowed("acme.vtex.app", {})).toBe(true);
363
+ expect(isDraftHostAllowed("acme.deco.site", {})).toBe(true);
364
364
  } finally {
365
365
  setDraftPreviewHosts([]);
366
366
  setDecoSiteHost(null);
@@ -368,11 +368,11 @@ describe("deco-hosted preview domains (setDecoSiteHost)", () => {
368
368
  });
369
369
 
370
370
  it("is merged ON TOP of the env escape hatch too", () => {
371
- setDecoSiteHost("als-storefront");
371
+ setDecoSiteHost("acme");
372
372
  try {
373
373
  const env = { DECO_ALLOWED_PREVIEW_HOSTS: "other.example" };
374
374
  expect(isDraftHostAllowed("other.example", env)).toBe(true);
375
- expect(isDraftHostAllowed("als-storefront.deco.site", env)).toBe(true);
375
+ expect(isDraftHostAllowed("acme.deco.site", env)).toBe(true);
376
376
  } finally {
377
377
  setDecoSiteHost(null);
378
378
  }
@@ -392,16 +392,16 @@ describe("deco-hosted preview domains (setDecoSiteHost)", () => {
392
392
  });
393
393
 
394
394
  it("DECO_ALLOWED_PREVIEW_HOSTS=none kills the inferred hosts too", () => {
395
- setDraftPreviewHosts(["fila.vtex.app"]);
396
- setDecoSiteHost("als-storefront");
395
+ setDraftPreviewHosts(["acme.vtex.app"]);
396
+ setDecoSiteHost("acme");
397
397
  try {
398
398
  // The kill switch wins over the inferred hosts AND the site block, so a
399
399
  // bad rollout can be stopped without a deploy.
400
400
  const env = { DECO_ALLOWED_PREVIEW_HOSTS: "none" };
401
401
  expect(isDraftPreviewEnabled(env)).toBe(false);
402
- expect(isDraftHostAllowed("als-storefront.deco.site", env)).toBe(false);
403
- expect(isDraftHostAllowed("als-storefront.deco-cx.workers.dev", env)).toBe(false);
404
- expect(isDraftHostAllowed("fila.vtex.app", env)).toBe(false);
402
+ expect(isDraftHostAllowed("acme.deco.site", env)).toBe(false);
403
+ expect(isDraftHostAllowed("acme.deco-cx.workers.dev", env)).toBe(false);
404
+ expect(isDraftHostAllowed("acme.vtex.app", env)).toBe(false);
405
405
  } finally {
406
406
  setDraftPreviewHosts([]);
407
407
  setDecoSiteHost(null);
@@ -179,7 +179,7 @@ export function setDraftPreviewHosts(hosts: readonly unknown[]): void {
179
179
  *
180
180
  * - `<site>.deco.site` — the stable deco-hosted domain.
181
181
  * - `<site>.deco-cx.workers.dev` — the workers.dev deploy URL
182
- * (e.g. `casaevideo-tanstack.deco-cx.workers.dev`).
182
+ * (e.g. `acme-tanstack.deco-cx.workers.dev`).
183
183
  *
184
184
  * Both are MERGED with the site-block/env list rather than replacing it: they
185
185
  * are deco-operated infra, so a signed draft grant can preview there out of the
@@ -13,7 +13,7 @@ import {
13
13
  * Regression test for the layout-cache index-corruption bug that shipped in
14
14
  * @decocms/start@6.12.1 and was fixed in 6.12.2 (blocks commit 73d4f19,
15
15
  * "fix(cms): don't mutate shared cached section objects when stamping
16
- * index"). Two live production sites (casaevideo-tanstack, bagaggio-tanstack)
16
+ * index"). Two live production sites
17
17
  * hit this in the wild as an intermittent "footer renders above other
18
18
  * sections" bug under concurrent traffic.
19
19
  *
@@ -78,32 +78,32 @@ describe("matchPath", () => {
78
78
  // These are the cases that issue #213 documents as broken.
79
79
 
80
80
  it("matches with the optional group present", () => {
81
- expect(matchPath("/{granado/}?*", "/granado/perfumaria")).toEqual({
81
+ expect(matchPath("/{acme/}?*", "/acme/perfumaria")).toEqual({
82
82
  "0": "perfumaria",
83
83
  });
84
84
  });
85
85
 
86
86
  it("matches with the optional group absent", () => {
87
- expect(matchPath("/{granado/}?*", "/perfumaria")).toEqual({
87
+ expect(matchPath("/{acme/}?*", "/perfumaria")).toEqual({
88
88
  "0": "perfumaria",
89
89
  });
90
90
  });
91
91
 
92
92
  it("matches root when optional prefix and splat collapse to empty", () => {
93
- expect(matchPath("/{granado/}?*", "/")).toEqual({ "0": "" });
93
+ expect(matchPath("/{acme/}?*", "/")).toEqual({ "0": "" });
94
94
  });
95
95
 
96
96
  it("matches with an optional prefix before a literal segment", () => {
97
97
  expect(
98
98
  matchPath(
99
- "/{granado/}?campanhas/*",
100
- "/granado/campanhas/destaques-2023",
99
+ "/{acme/}?campanhas/*",
100
+ "/acme/campanhas/destaques-2023",
101
101
  ),
102
102
  ).toEqual({
103
103
  "0": "destaques-2023",
104
104
  });
105
105
  expect(
106
- matchPath("/{granado/}?campanhas/*", "/campanhas/destaques-2023"),
106
+ matchPath("/{acme/}?campanhas/*", "/campanhas/destaques-2023"),
107
107
  ).toEqual({
108
108
  "0": "destaques-2023",
109
109
  });
@@ -158,7 +158,7 @@ describe("findPageByPath specificity", () => {
158
158
  },
159
159
  "pages-pdp-plp": {
160
160
  name: "PDP & PLP",
161
- path: "/{granado/}?*",
161
+ path: "/{acme/}?*",
162
162
  sections: [],
163
163
  },
164
164
  "pages-product": {
@@ -179,8 +179,8 @@ describe("findPageByPath specificity", () => {
179
179
  });
180
180
 
181
181
  it("prefers the home page over an optional-group splat catch-all", () => {
182
- // Regression: /{granado/}?* matches "/" and was out-ranking the home
183
- // because the `{granado` segment counted as a param. The home block
182
+ // Regression: /{acme/}?* matches "/" and was out-ranking the home
183
+ // because the `{acme` segment counted as a param. The home block
184
184
  // is a literal-only `/` path and must always win.
185
185
  setBlocks({
186
186
  "pages-home": {
@@ -190,7 +190,7 @@ describe("findPageByPath specificity", () => {
190
190
  },
191
191
  "pages-pdp-plp": {
192
192
  name: "PDP & PLP",
193
- path: "/{granado/}?*",
193
+ path: "/{acme/}?*",
194
194
  sections: [],
195
195
  },
196
196
  });
@@ -228,7 +228,7 @@ describe("loadBlocks draft override — key percent-encoding", () => {
228
228
  // (`pages-Home (principal)-1`). Under snapshot semantics the draft REPLACES
229
229
  // the file-backed base wholesale, so an encoded/raw twin pair can never
230
230
  // coexist — these regression tests (from the merge era, when ~73% of
231
- // casaevideo's pages silently ignored drafts) now pin that property.
231
+ // one production site's pages silently ignored drafts) now pin that property.
232
232
 
233
233
  afterEach(() => {
234
234
  setBlocks({});
package/src/cms/loader.ts CHANGED
@@ -254,13 +254,13 @@ export function withDraftBlocks<T>(
254
254
  // `/` (literals=0) vs the catch-all `/{prefix/}?*` (literals=0, params=1).
255
255
  // Without this, the URLPattern fix (#213/#214) inadvertently lets a
256
256
  // `/{group/}?*` catch-all out-rank an exact `/` home page because the
257
- // `{group` segment counted as a param. See deco-sites/granadobr-tanstack
258
- // where `/` was being routed to the granado PDP/PLP block's NotFound
257
+ // `{group` segment counted as a param. See a production storefront
258
+ // where `/` was being routed to a brand-prefixed PDP/PLP block's NotFound
259
259
  // fallback.
260
260
  //
261
261
  // Order produced:
262
262
  // /foo/bar (no wildcard, literals=2) > /foo/:x (no wildcard, lit=1, param=1)
263
- // /foo (no wildcard) > /{granado/}?* (has wildcard) > /*
263
+ // /foo (no wildcard) > /{acme/}?* (has wildcard) > /*
264
264
  function pathSpecificityKey(path: string): [number, number, number] {
265
265
  const parts = path.split("/").filter(Boolean);
266
266
  let literals = 0;
@@ -816,7 +816,7 @@ describe("extractSeoFromProps — commerce jsonLD structured data", () => {
816
816
  });
817
817
 
818
818
  it("derives title/description/canonical and emits the ItemList JSON-LD (PLP)", () => {
819
- // The casaevideo.com.br/eletroportateis regression: a page.seo pointing at
819
+ // The acme.com.br/eletroportateis regression: a page.seo pointing at
820
820
  // commerce/sections/Seo/SeoPLPV2.tsx resolved its jsonLD but emitted no
821
821
  // schema.org. There is no section component in start to run this transform.
822
822
  const seo = extractSeoFromProps({
@@ -1982,7 +1982,7 @@ function pruneCommerceJsonLD(
1982
1982
  * ship loaders/types, not sections), so nothing else runs this transform.
1983
1983
  * Without it, PLP/PDP pages whose `page.seo` points at those types emit a
1984
1984
  * <title>/canonical (from top-level override props) but NO schema.org
1985
- * structured data — the casaevideo.com.br/eletroportateis regression.
1985
+ * structured data — the acme.com.br/eletroportateis regression.
1986
1986
  *
1987
1987
  * Manual override fields already read into `seo` always win; this only fills
1988
1988
  * gaps and appends the JSON-LD. The caller guards on `seo.jsonLDs` so a real
@@ -1,4 +1,5 @@
1
1
  import React, { useEffect } from "react";
2
+ import { getRequestNonce } from "../sdk/nonce";
2
3
 
3
4
  interface LiveControlsProps {
4
5
  site?: string;
@@ -135,5 +136,7 @@ function LiveControlsScript() {
135
136
  })();
136
137
  `;
137
138
 
138
- return <script type="module" dangerouslySetInnerHTML={{ __html: script }} />;
139
+ return (
140
+ <script type="module" nonce={getRequestNonce()} dangerouslySetInnerHTML={{ __html: script }} />
141
+ );
139
142
  }
@@ -309,7 +309,7 @@ describe("normalizePath cardinality", () => {
309
309
  it("collapses CMS content slugs", () => {
310
310
  expect(normalizePath("/mochila-de-couro")).toBe("/:slug");
311
311
  expect(normalizePath("/moveis/quarto-infantil")).toBe("/moveis/:slug");
312
- expect(normalizePath("/granado/eau-de-toilette-spritz-100ml")).toBe("/granado/:slug");
312
+ expect(normalizePath("/acme/eau-de-toilette-spritz-100ml")).toBe("/acme/:slug");
313
313
  expect(normalizePath("/moveis/quarto-adulto/cabeceiras-mesa-de-cabeceiras")).toBe(
314
314
  "/moveis/:slug/:slug",
315
315
  );
@@ -737,7 +737,7 @@ export function normalizePath(path: string): string {
737
737
  //
738
738
  // /mochila-de-couro
739
739
  // /moveis/quarto-infantil
740
- // /granado/eau-de-toilette-spritz-100ml
740
+ // /acme/eau-de-toilette-spritz-100ml
741
741
  //
742
742
  // Measured on the production ClickHouse, `http.route` reached 20,714
743
743
  // distinct values across only 6 tenants — on `http.server.request.duration`,
@@ -11,7 +11,7 @@ describe("DraftPreviewBadge (render)", () => {
11
11
  // pass, actually ships. The badge fails closed: nothing here, by design
12
12
  // (see the module doc for why render-then-hide is worse).
13
13
  it("renders nothing without a client to run the reveal effect", () => {
14
- const html = renderToString(<DraftPreviewBadge pointer="fila.vtex.app@v1" />);
14
+ const html = renderToString(<DraftPreviewBadge pointer="acme.vtex.app@v1" />);
15
15
  expect(html).toBe("");
16
16
  });
17
17
 
@@ -34,14 +34,14 @@ describe("DraftPreviewBadge (render)", () => {
34
34
 
35
35
  it("reveals the preview-mode label after mount, once confirmed unframed", () => {
36
36
  act(() => {
37
- root.render(<DraftPreviewBadge pointer="fila.vtex.app@v1" />);
37
+ root.render(<DraftPreviewBadge pointer="acme.vtex.app@v1" />);
38
38
  });
39
39
  expect(container.textContent).toContain("Preview mode");
40
40
  });
41
41
 
42
42
  it("embeds the deco mark inline (no external asset that could 404 on a consumer site)", () => {
43
43
  act(() => {
44
- root.render(<DraftPreviewBadge pointer="fila.vtex.app@v1" />);
44
+ root.render(<DraftPreviewBadge pointer="acme.vtex.app@v1" />);
45
45
  });
46
46
  expect(container.innerHTML).toContain("data:image/png;base64,");
47
47
  });
@@ -58,7 +58,7 @@ describe("DraftPreviewBadge (render)", () => {
58
58
  it("stays hidden — Studio's own preview surface already has its own chrome", () => {
59
59
  Object.defineProperty(window, "top", { value: {}, configurable: true });
60
60
  act(() => {
61
- root.render(<DraftPreviewBadge pointer="fila.vtex.app@v1" />);
61
+ root.render(<DraftPreviewBadge pointer="acme.vtex.app@v1" />);
62
62
  });
63
63
  expect(container.textContent).toBe("");
64
64
  });
@@ -4,34 +4,34 @@ import { buildExitUrl, buildShareUrl, isFramed } from "./DraftPreviewBadge";
4
4
 
5
5
  describe("buildShareUrl", () => {
6
6
  it("pins the exact draft version so a recipient sees the same working tree", () => {
7
- expect(buildShareUrl("https://fila.vtex.app/pdp/shoe", "abc.localhost:60534@v7")).toBe(
8
- "https://fila.vtex.app/pdp/shoe?__draft=abc.localhost%3A60534%40v7",
7
+ expect(buildShareUrl("https://acme.vtex.app/pdp/shoe", "abc.localhost:60534@v7")).toBe(
8
+ "https://acme.vtex.app/pdp/shoe?__draft=abc.localhost%3A60534%40v7",
9
9
  );
10
10
  });
11
11
 
12
12
  it("preserves existing query params, only setting the draft token", () => {
13
- expect(buildShareUrl("https://fila.vtex.app/plp?sort=price", "h@v1")).toBe(
14
- "https://fila.vtex.app/plp?sort=price&__draft=h%40v1",
13
+ expect(buildShareUrl("https://acme.vtex.app/plp?sort=price", "h@v1")).toBe(
14
+ "https://acme.vtex.app/plp?sort=price&__draft=h%40v1",
15
15
  );
16
16
  });
17
17
 
18
18
  it("replaces a stale token already in the URL rather than appending a second", () => {
19
- expect(buildShareUrl("https://fila.vtex.app/p?__draft=h@old", "h@new")).toBe(
20
- "https://fila.vtex.app/p?__draft=h%40new",
19
+ expect(buildShareUrl("https://acme.vtex.app/p?__draft=h@old", "h@new")).toBe(
20
+ "https://acme.vtex.app/p?__draft=h%40new",
21
21
  );
22
22
  });
23
23
  });
24
24
 
25
25
  describe("buildExitUrl", () => {
26
26
  it("sets the ?__draft=off exit sentinel", () => {
27
- expect(buildExitUrl("https://fila.vtex.app/pdp/shoe")).toBe(
28
- "https://fila.vtex.app/pdp/shoe?__draft=off",
27
+ expect(buildExitUrl("https://acme.vtex.app/pdp/shoe")).toBe(
28
+ "https://acme.vtex.app/pdp/shoe?__draft=off",
29
29
  );
30
30
  });
31
31
 
32
32
  it("overrides an active token with off (leaves preview from anywhere)", () => {
33
- expect(buildExitUrl("https://fila.vtex.app/p?__draft=h@v1")).toBe(
34
- "https://fila.vtex.app/p?__draft=off",
33
+ expect(buildExitUrl("https://acme.vtex.app/p?__draft=h@v1")).toBe(
34
+ "https://acme.vtex.app/p?__draft=off",
35
35
  );
36
36
  });
37
37
  });
@@ -12,8 +12,8 @@ import {
12
12
  // Test helpers
13
13
  // ---------------------------------------------------------------------------
14
14
 
15
- const REAL_HOST = "www.bagaggio.com.br";
16
- const FALLBACK_HOST = "lojabagaggio.deco.site";
15
+ const REAL_HOST = "www.acme.com.br";
16
+ const FALLBACK_HOST = "lojaacme.deco.site";
17
17
 
18
18
  function makeUrl(path = "/x"): URL {
19
19
  return new URL(`https://${REAL_HOST}${path}`);
@@ -305,16 +305,16 @@ describe("readExperimentConfig", () => {
305
305
  const kv = (value: unknown) => ({ get: async () => value as never });
306
306
 
307
307
  it("reads contract 1's document", async () => {
308
- expect(await readExperimentConfig(kv(CONFIG), "www.farmrio.com")).toEqual(CONFIG);
308
+ expect(await readExperimentConfig(kv(CONFIG), "www.acme.com")).toEqual(CONFIG);
309
309
  });
310
310
 
311
311
  it("returns null instead of throwing when the binding is missing", async () => {
312
- expect(await readExperimentConfig(undefined, "www.farmrio.com")).toBeNull();
312
+ expect(await readExperimentConfig(undefined, "www.acme.com")).toBeNull();
313
313
  });
314
314
 
315
315
  it("returns null on an unset key or a malformed document", async () => {
316
- expect(await readExperimentConfig(kv(null), "www.farmrio.com")).toBeNull();
317
- expect(await readExperimentConfig(kv({ version: 1 }), "www.farmrio.com")).toBeNull();
316
+ expect(await readExperimentConfig(kv(null), "www.acme.com")).toBeNull();
317
+ expect(await readExperimentConfig(kv({ version: 1 }), "www.acme.com")).toBeNull();
318
318
  });
319
319
 
320
320
  it("returns null when KV itself fails — never a thrown request", async () => {
@@ -323,6 +323,6 @@ describe("readExperimentConfig", () => {
323
323
  throw new Error("KV unavailable");
324
324
  },
325
325
  };
326
- expect(await readExperimentConfig(broken, "www.farmrio.com")).toBeNull();
326
+ expect(await readExperimentConfig(broken, "www.acme.com")).toBeNull();
327
327
  });
328
328
  });
@@ -185,7 +185,7 @@ describe("setLoggerAttributeFloor", () => {
185
185
  });
186
186
 
187
187
  it("applies the floor even when the caller passes no attrs", () => {
188
- setLoggerAttributeFloor({ tenant: "lebiscuit" });
188
+ setLoggerAttributeFloor({ tenant: "acme" });
189
189
  const calls: Array<Record<string, unknown> | undefined> = [];
190
190
  configureLogger({
191
191
  log(_level, _msg, attrs) {
@@ -195,11 +195,11 @@ describe("setLoggerAttributeFloor", () => {
195
195
 
196
196
  logger.info("no-attrs");
197
197
 
198
- expect(calls[0]).toEqual({ tenant: "lebiscuit" });
198
+ expect(calls[0]).toEqual({ tenant: "acme" });
199
199
  });
200
200
 
201
201
  it("clearing the floor restores the no-op fast path", () => {
202
- setLoggerAttributeFloor({ tenant: "lebiscuit" });
202
+ setLoggerAttributeFloor({ tenant: "acme" });
203
203
  setLoggerAttributeFloor({});
204
204
  const calls: Array<Record<string, unknown> | undefined> = [];
205
205
  configureLogger({
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Per-request CSP nonce.
3
+ *
4
+ * When the TanStack worker entry runs with `cspMode: "enforce"`, it generates a
5
+ * fresh nonce per request, stashes it in the RequestContext bag (below), and
6
+ * threads it into:
7
+ *
8
+ * - the TanStack router (`router.options.ssr.nonce`), so `ScriptOnce` and the
9
+ * framework's own hydration scripts carry `nonce=…`;
10
+ * - the hand-written inline `<script>` tags in the deco layout / live-controls
11
+ * components (they read it via `getRequestNonce()`);
12
+ * - the enforced `Content-Security-Policy` header's `script-src`.
13
+ *
14
+ * Lives in `runtime` (not `tanstack`) so both the tanstack worker and the
15
+ * runtime components (e.g. LiveControls) can reach it without crossing the
16
+ * one-way package dependency graph.
17
+ *
18
+ * The nonce is only produced on the server and only in enforce mode; in the
19
+ * browser the RequestContext is a no-op stub, so `getRequestNonce()` returns
20
+ * `undefined` and every consumer degrades to "no nonce attribute".
21
+ */
22
+ import { RequestContext } from "./requestContext";
23
+
24
+ /** RequestContext bag key holding the current request's CSP nonce. */
25
+ export const NONCE_BAG_KEY = "deco.csp.nonce";
26
+
27
+ /**
28
+ * Generate a CSP nonce: 128 bits of CSPRNG randomness, base64-encoded. Fresh
29
+ * per request — never reuse across responses, or the nonce loses all value.
30
+ */
31
+ export function generateNonce(): string {
32
+ const bytes = new Uint8Array(16);
33
+ crypto.getRandomValues(bytes);
34
+ let binary = "";
35
+ for (const byte of bytes) binary += String.fromCharCode(byte);
36
+ return btoa(binary);
37
+ }
38
+
39
+ /**
40
+ * The current request's CSP nonce, or `undefined` when CSP is not enforced
41
+ * (report-only mode) or on the client. Read this in an SSR component to nonce
42
+ * an inline `<script>`: `<script nonce={getRequestNonce()} … />`.
43
+ */
44
+ export function getRequestNonce(): string | undefined {
45
+ return RequestContext.getBag<string>(NONCE_BAG_KEY);
46
+ }
@@ -13,8 +13,8 @@
13
13
  * // In site's setup.ts:
14
14
  * import { registerProductionOrigins } from "@decocms/start/sdk/normalizeUrls";
15
15
  * registerProductionOrigins([
16
- * "https://www.casaevideo.com.br",
17
- * "https://casaevideo.com.br",
16
+ * "https://www.acme.com.br",
17
+ * "https://acme.com.br",
18
18
  * ]);
19
19
  * ```
20
20
  */
@@ -169,13 +169,13 @@ describe("instrumentWorker — identity floor", () => {
169
169
 
170
170
  it("stamps service.name and service.version on the logger floor", async () => {
171
171
  const handler = { fetch: vi.fn().mockResolvedValue(new Response("ok")) };
172
- const wrapped = instrumentWorker(handler, { serviceName: "casaevideo-tanstack" });
172
+ const wrapped = instrumentWorker(handler, { serviceName: "acme-tanstack" });
173
173
 
174
174
  const env: TestEnv = { CF_VERSION_METADATA: { id: "abc123" } };
175
175
  await wrapped.fetch(new Request("https://example.test/"), env, fakeCtx());
176
176
 
177
177
  const floor = logger._getLoggerAttributeFloorForTests();
178
- expect(floor["service.name"]).toBe("casaevideo-tanstack");
178
+ expect(floor["service.name"]).toBe("acme-tanstack");
179
179
  expect(floor["service.version"]).toBe("abc123");
180
180
  });
181
181
 
package/src/sdk/otel.ts CHANGED
@@ -312,7 +312,7 @@ interface WorkerHandler {
312
312
  // into Copy A's `otlpMeter` while the per-request `recordRequestMetric`
313
313
  // closure reads from Copy B's empty `otlpMeter`. Net effect in prod:
314
314
  // `bootObservability` runs (we observed the very first `POST /v1/metrics`
315
- // from miess-tanstack), but every subsequent request's `flush()` finds an
315
+ // from a production storefront), but every subsequent request's `flush()` finds an
316
316
  // empty buffer because the meter the framework writes into is a different
317
317
  // instance from the one the exporter drains.
318
318
  //
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Search-suggestions hook factory.
3
3
  *
4
- * Both casaevideo-storefront and baggagio-tanstack independently
4
+ * Two production storefronts independently
5
5
  * invented the same shape for autocomplete-style suggestions:
6
6
  *
7
7
  * - module-level signal for the current payload + loading flag