hazo_blog 0.3.0 → 0.3.3

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/CHANGE_LOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## 0.3.3 — 2026-07-06
7
+
8
+ **`sanitizeMdx` hardened against raw WordPress HTML**
9
+
10
+ Extends the sanitizer beyond HTML comments to cover three more parser-breaking patterns commonly
11
+ found in imported WordPress post bodies. All run on the code-masked string, so fenced/inline code
12
+ spans stay byte-for-byte untouched, and the transform remains idempotent.
13
+
14
+ - **`<script>` / `<style>` blocks** — dropped entirely. Their bodies (JSON-LD, inline CSS) contain
15
+ raw `{ }` that MDX parses as a JS expression, throwing "Could not parse expression with acorn".
16
+ They carry no visible prose, so removing them never changes rendered output.
17
+ - **HTML void elements** (`<img>`, `<br>`, `<hr>`, `<input>`, …) — normalized to self-closing
18
+ (`<img src="x" />`). Un-self-closed voids throw "Expected a closing tag" in MDX/JSX.
19
+ - **String-valued `style="..."` / `style='...'` attributes** — dropped. Valid HTML but invalid JSX
20
+ (React requires the object form `style={{...}}`), else "The style prop expects a mapping … not a
21
+ string" at render. The JSX object form is deliberately left untouched.
22
+
23
+ No API change — same `sanitizeMdx(mdx: string): string` signature. **Consumer note:** `^0.3.0`
24
+ resolves to `0.3.3` (caret allows patch/minor within the same pre-1.0 minor).
25
+
6
26
  ## 0.3.0 — 2026-06-12
7
27
 
8
28
  **Build resilience: sanitizer + async try/catch guard against malformed MDX**
package/README.md CHANGED
@@ -208,9 +208,14 @@ const nextConfig = {
208
208
 
209
209
  `BlogContent` (≥ 0.3.0) is an async server component that catches MDX compilation errors with
210
210
  `try/catch`, so a single malformed post renders a graceful fallback instead of failing the whole
211
- `next build`. It also pre-sanitizes content with `sanitizeMdx` (converts HTML comments MDX
212
- comments, escapes stray `<!`) before passing it to the compiler. The sanitizer is also available
213
- as a standalone export from `hazo_blog/lib` for custom rendering pipelines.
211
+ `next build`. It also pre-sanitizes content with `sanitizeMdx` before passing it to the compiler.
212
+ The sanitizer fixes the common ways raw WordPress HTML trips the MDX/acorn parser: converts HTML
213
+ comments MDX comments (and escapes stray `<!`), strips `<script>`/`<style>` blocks whose bodies
214
+ break parsing, self-closes HTML void elements (`<img>`, `<br>`, …), and drops string-valued
215
+ `style="..."` attributes (invalid JSX; the object form `style={{...}}` is left untouched). Fenced
216
+ and inline code spans are left byte-for-byte untouched, and the transform is idempotent. The
217
+ sanitizer is also available as a standalone export from `hazo_blog/lib` for custom rendering
218
+ pipelines.
214
219
 
215
220
  ## Exports
216
221
 
@@ -23,13 +23,33 @@ export interface TocHeading {
23
23
  }
24
24
  export declare function extractToc(mdx: string): TocHeading[];
25
25
  /**
26
- * Make raw MDX safe to compile. MDX has no HTML comments a `<!--` in prose
27
- * makes the parser expect a JSX tag name after `<` and throw "Unexpected
28
- * character `!` before name", crashing the whole Next build at prerender.
29
- * Convert HTML comments to MDX comments ({/*…*\/}, equally non-rendering) and
30
- * escape any stray `<!`, while leaving fenced/inline code untouched (it is
31
- * literal in MDX, never errors, and may legitimately show an HTML comment in a
32
- * sample). Idempotent: after one pass no literal `<!` survives in prose.
26
+ * Make raw MDX safe to compile. Raw WordPress HTML stored as post content
27
+ * trips the MDX/acorn parser in a handful of unambiguously-fixable ways:
28
+ *
29
+ * 1. HTML comments MDX has none, so a bare `<!--` makes the parser expect
30
+ * a JSX tag name after `<` and throw "Unexpected character `!` before
31
+ * name". Converted to MDX comments ({/*…*\/}, equally non-rendering).
32
+ * 2. `<script>`/`<style>` blocks their bodies (JSON-LD, inline CSS)
33
+ * contain raw `{ }` that MDX parses as a JS expression, throwing "Could
34
+ * not parse expression with acorn". They carry no visible prose (SEO
35
+ * metadata / global CSS handle those concerns elsewhere), so dropping the
36
+ * whole block is safe and never changes rendered output.
37
+ * 3. HTML void elements (`<img>`, `<br>`, …) written un-self-closed — MDX/JSX
38
+ * requires them self-closed; `<img src="x">` alone throws "Expected a
39
+ * closing tag". Normalized to `<img src="x" />` regardless of whether a
40
+ * stray slash was already present.
41
+ * 4. String-valued `style="..."` / `style='...'` attributes — valid HTML but
42
+ * invalid JSX/MDX (React requires `style={{...}}`, an object, never a
43
+ * string); left alone, they compile fine but throw "The style prop
44
+ * expects a mapping ... not a string" at render time. Always broken, so
45
+ * the attribute is dropped outright. Only the quoted-string form is
46
+ * matched (`style="`/`style='`) — the legitimate JSX object form
47
+ * (`style={{...}}`) never matches and is left untouched.
48
+ *
49
+ * All four run on the code-MASKED string, so fenced/inline code spans are
50
+ * left byte-for-byte untouched (they are literal in MDX, never error, and
51
+ * may legitimately show a comment, a `<script>` sample, or a bare `<img>` in
52
+ * a code sample). Idempotent: running this twice yields the same output.
33
53
  */
34
54
  export declare function sanitizeMdx(mdx: string): string;
35
55
  //# sourceMappingURL=text.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/lib/text.ts"],"names":[],"mappings":"AAGA,6CAA6C;AAC7C,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAalD;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,SAAM,GAAG,MAAM,CAG9E;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAM,GAAG,MAAM,CAMhE;AAED,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAYpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkB/C"}
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/lib/text.ts"],"names":[],"mappings":"AAGA,6CAA6C;AAC7C,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS7C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAalD;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,SAAM,GAAG,MAAM,CAG9E;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAM,GAAG,MAAM,CAMhE;AAED,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,CAYpD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA+B/C"}
package/dist/lib/text.js CHANGED
@@ -68,13 +68,33 @@ export function extractToc(mdx) {
68
68
  return headings;
69
69
  }
70
70
  /**
71
- * Make raw MDX safe to compile. MDX has no HTML comments a `<!--` in prose
72
- * makes the parser expect a JSX tag name after `<` and throw "Unexpected
73
- * character `!` before name", crashing the whole Next build at prerender.
74
- * Convert HTML comments to MDX comments ({/*…*\/}, equally non-rendering) and
75
- * escape any stray `<!`, while leaving fenced/inline code untouched (it is
76
- * literal in MDX, never errors, and may legitimately show an HTML comment in a
77
- * sample). Idempotent: after one pass no literal `<!` survives in prose.
71
+ * Make raw MDX safe to compile. Raw WordPress HTML stored as post content
72
+ * trips the MDX/acorn parser in a handful of unambiguously-fixable ways:
73
+ *
74
+ * 1. HTML comments MDX has none, so a bare `<!--` makes the parser expect
75
+ * a JSX tag name after `<` and throw "Unexpected character `!` before
76
+ * name". Converted to MDX comments ({/*…*\/}, equally non-rendering).
77
+ * 2. `<script>`/`<style>` blocks their bodies (JSON-LD, inline CSS)
78
+ * contain raw `{ }` that MDX parses as a JS expression, throwing "Could
79
+ * not parse expression with acorn". They carry no visible prose (SEO
80
+ * metadata / global CSS handle those concerns elsewhere), so dropping the
81
+ * whole block is safe and never changes rendered output.
82
+ * 3. HTML void elements (`<img>`, `<br>`, …) written un-self-closed — MDX/JSX
83
+ * requires them self-closed; `<img src="x">` alone throws "Expected a
84
+ * closing tag". Normalized to `<img src="x" />` regardless of whether a
85
+ * stray slash was already present.
86
+ * 4. String-valued `style="..."` / `style='...'` attributes — valid HTML but
87
+ * invalid JSX/MDX (React requires `style={{...}}`, an object, never a
88
+ * string); left alone, they compile fine but throw "The style prop
89
+ * expects a mapping ... not a string" at render time. Always broken, so
90
+ * the attribute is dropped outright. Only the quoted-string form is
91
+ * matched (`style="`/`style='`) — the legitimate JSX object form
92
+ * (`style={{...}}`) never matches and is left untouched.
93
+ *
94
+ * All four run on the code-MASKED string, so fenced/inline code spans are
95
+ * left byte-for-byte untouched (they are literal in MDX, never error, and
96
+ * may legitimately show a comment, a `<script>` sample, or a bare `<img>` in
97
+ * a code sample). Idempotent: running this twice yields the same output.
78
98
  */
79
99
  export function sanitizeMdx(mdx) {
80
100
  const stash = [];
@@ -90,8 +110,18 @@ export function sanitizeMdx(mdx) {
90
110
  .replace(/~~~[\s\S]*?~~~/g, keep)
91
111
  .replace(/`[^`\n]*`/g, keep);
92
112
  masked = masked
113
+ // Strip <script>/<style> blocks entirely — see rationale above.
114
+ .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "")
115
+ .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "")
93
116
  // Comment body: neutralize any `*/` so it can't close the MDX comment early.
94
117
  .replace(/<!--([\s\S]*?)-->/g, (_, body) => `{/*${body.replace(/\*\//g, "* /")}*/}`)
95
- .replace(/<!(?!--)/g, "&lt;!");
118
+ .replace(/<!(?!--)/g, "&lt;!")
119
+ // Strip string-valued `style="..."` attributes — see rationale above.
120
+ // Deliberately does NOT match `style={` (the valid JSX object form).
121
+ .replace(/\sstyle=("[^"]*"|'[^']*')/gi, "")
122
+ // Self-close HTML void elements — see rationale above. Emits `/>`
123
+ // whether or not a slash was already present, so a second pass is a
124
+ // no-op (idempotent).
125
+ .replace(/<(img|br|hr|input|source|meta|link|col|area|base|embed|param|track|wbr)\b([^>]*?)\s*\/?>/gi, "<$1$2 />");
96
126
  return masked.replace(/(\d+)/g, (_, i) => stash[Number(i)]);
97
127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_blog",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "SEO-optimized blogging package: posts, categories, tags, MDX content, and GA4/GSC/Bing-ready SEO.",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -44,14 +44,14 @@
44
44
  "build:test-app": "npm run build && cd test-app && npm run build"
45
45
  },
46
46
  "peerDependencies": {
47
- "hazo_core": "^1.2.0",
47
+ "hazo_core": "^1.2.1",
48
48
  "hazo_connect": "^3.9.0",
49
- "hazo_api": "^2.4.0",
50
- "hazo_files": "^3.1.0",
51
- "hazo_ui": "^4.6.2",
52
- "hazo_images": "^1.2.1",
53
- "hazo_jobs": "^0.12.0",
54
- "hazo_auth": "^10.0.0",
49
+ "hazo_api": "^2.5.1",
50
+ "hazo_files": "^3.1.1",
51
+ "hazo_ui": "^4.8.0",
52
+ "hazo_images": "^1.7.0",
53
+ "hazo_jobs": "^0.14.0",
54
+ "hazo_auth": "^10.5.0",
55
55
  "react": "^18.0.0 || ^19.0.0",
56
56
  "react-dom": "^18.0.0 || ^19.0.0",
57
57
  "next": "^14.0.0 || ^16.0.0"
@@ -87,7 +87,7 @@
87
87
  "hazo_connect": "^3.9.0",
88
88
  "hazo_api": "^2.5.1",
89
89
  "hazo_files": "^3.1.1",
90
- "hazo_ui": "^4.7.0",
90
+ "hazo_ui": "^4.8.0",
91
91
  "tailwindcss": "^4.2.4",
92
92
  "@tailwindcss/postcss": "^4.2.4",
93
93
  "postcss": "^8.4.49"