dh-remixer-sdk 0.0.38 → 0.0.39-2e1e75f

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": "dh-remixer-sdk",
3
- "version": "0.0.38",
3
+ "version": "0.0.39-2e1e75f",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "templates",
@@ -64,6 +64,9 @@ const setForce = (obj, k, v) => {
64
64
  obj[k] = s;
65
65
  };
66
66
 
67
+ const isHelmetOwned = (attrs) =>
68
+ "data-rh" in attrs || "data-react-helmet" in attrs;
69
+
67
70
  const normalizeDomain = (d) => {
68
71
  const s = String(d || "").trim();
69
72
  if (!s) return "";
@@ -185,6 +188,7 @@ export async function injectMetadata(
185
188
  // metas
186
189
  for (const tag of metaTags) {
187
190
  const attrs = parseAttrs(tag);
191
+ const set = isHelmetOwned(attrs) ? setForce : setFirst;
188
192
 
189
193
  if (attrs.charset) {
190
194
  setFirst(meta, "charset", decodeHtml(attrs.charset));
@@ -196,20 +200,20 @@ export async function injectMetadata(
196
200
  const content = decodeHtml(attrs.content ?? "");
197
201
 
198
202
  if (name === "description") {
199
- setFirst(meta, "description", content);
203
+ set(meta, "description", content);
200
204
  } else if (name === "robots") {
201
- setFirst(meta, "robots", content);
205
+ set(meta, "robots", content);
202
206
  } else if (name === "viewport") {
203
- setFirst(meta, "viewport", content);
207
+ set(meta, "viewport", content);
204
208
  } else if (name === "theme-color") {
205
- setFirst(meta, "theme-color", content);
209
+ set(meta, "theme-color", content);
206
210
  } else if (name && name in meta) {
207
- setFirst(meta, name, content);
211
+ set(meta, name, content);
208
212
  }
209
213
 
210
- if (prop && prop in meta) setFirst(meta, prop, content);
214
+ if (prop && prop in meta) set(meta, prop, content);
211
215
 
212
- if (httpEquiv === "content-language") setFirst(meta, "og:locale", content);
216
+ if (httpEquiv === "content-language") set(meta, "og:locale", content);
213
217
  }
214
218
 
215
219
  // canonical from <link rel="canonical"...> if present in source
@@ -39,3 +39,68 @@ test("description uses the name attribute", async () => {
39
39
  const out = await injectMetadata(SAMPLE, "/", true);
40
40
  assert.match(out, /<meta\s+name="description"/);
41
41
  });
42
+
43
+ // A real prerendered subpage: index.html's static, site-wide metas (built from
44
+ // VITE_METADATA_*) sit first in <head>, then react-helmet-async appends the
45
+ // page-level ones with data-rh="true" on hydration.
46
+ const SITE_DESCRIPTION = "Site-wide description from VITE_METADATA_DESCRIPTION";
47
+ const PAGE_DESCRIPTION = "Page-specific description from SEOHead";
48
+ const HYDRATED_SUBPAGE = `<!doctype html>
49
+ <html lang="en">
50
+ <head>
51
+ <meta charset="UTF-8" />
52
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
53
+ <title>Services</title>
54
+ <meta name="description" content="${SITE_DESCRIPTION}" />
55
+ <meta name="description" content="${PAGE_DESCRIPTION}" data-rh="true" />
56
+ <meta property="og:description" content="${PAGE_DESCRIPTION}" data-rh="true" />
57
+ </head>
58
+ <body class="antialiased"><div id="root"></div></body>
59
+ </html>`;
60
+
61
+ test("page-level helmet description wins over the static one (AI-7092)", async () => {
62
+ const out = await injectMetadata(HYDRATED_SUBPAGE, "/services", true);
63
+ assert.match(
64
+ out,
65
+ new RegExp(`<meta\\s+name="description"\\s+content="${PAGE_DESCRIPTION}"`),
66
+ );
67
+ assert.doesNotMatch(out, new RegExp(SITE_DESCRIPTION));
68
+ });
69
+
70
+ test("only one description tag is emitted", async () => {
71
+ const out = await injectMetadata(HYDRATED_SUBPAGE, "/services", true);
72
+ assert.equal(out.match(/<meta\s+name="description"/g)?.length, 1);
73
+ });
74
+
75
+ test("JSON-LD description follows the page-level description (AI-7092)", async () => {
76
+ const out = await injectMetadata(HYDRATED_SUBPAGE, "/services", true);
77
+ const ldJson = out.match(
78
+ /<script type="application\/ld\+json">([\s\S]*?)<\/script>/,
79
+ )?.[1];
80
+ assert.ok(ldJson, "expected a JSON-LD block");
81
+ assert.equal(JSON.parse(ldJson).description, PAGE_DESCRIPTION);
82
+ });
83
+
84
+ test("a head with only the static description still keeps it", async () => {
85
+ const out = await injectMetadata(SAMPLE, "/", true);
86
+ assert.match(out, /<meta\s+name="description"\s+content="A sample description"/);
87
+ });
88
+
89
+ // og:locale is derived from http-equiv="content-language", which follows the
90
+ // same precedence rule as every other meta: a helmet-owned tag beats the static one.
91
+ const LOCALIZED_PAGE = `<!doctype html>
92
+ <html lang="en">
93
+ <head>
94
+ <meta charset="UTF-8" />
95
+ <meta http-equiv="content-language" content="en_US" />
96
+ <meta http-equiv="content-language" content="fr_FR" data-rh="true" />
97
+ <title>Services</title>
98
+ </head>
99
+ <body class="antialiased"><div id="root"></div></body>
100
+ </html>`;
101
+
102
+ test("helmet content-language wins over the static one for og:locale", async () => {
103
+ const out = await injectMetadata(LOCALIZED_PAGE, "/services", true);
104
+ assert.match(out, /<meta\s+property="og:locale"\s+content="fr_FR"/);
105
+ assert.doesNotMatch(out, /content="en_US"/);
106
+ });
@@ -27,6 +27,7 @@
27
27
  "hls.js": "1.6.16",
28
28
  "lenis": "1.3.25",
29
29
  "lucide-react": "0.554.0",
30
+ "motion": "12.23.24",
30
31
  "motion-dom": "12.40.0",
31
32
  "postprocessing": "6.37.0",
32
33
  "react": "19.2.0",