@docubook/flame 1.3.3 → 1.3.5

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.
@@ -112,8 +112,8 @@ async function renderDocsPage(
112
112
  });
113
113
  }
114
114
 
115
- const title = (frontmatter.title as string) || slug || "Docs";
116
- const description = (frontmatter.description as string) || "";
115
+ const title = (typeof frontmatter.title === "string" ? frontmatter.title : "") || slug || "Docs";
116
+ const description = typeof frontmatter.description === "string" ? frontmatter.description : "";
117
117
  const slugParts = slug ? slug.split("/") : [];
118
118
 
119
119
  const page = React.createElement(
@@ -52,11 +52,7 @@ export class BuildPluginBuilder implements PluginBuilder {
52
52
  try {
53
53
  const result = cb(context);
54
54
  if (result) {
55
- if (Array.isArray(result)) {
56
- items.push(...result);
57
- } else {
58
- items.push(result);
59
- }
55
+ this.collectItems(items, result, "injectBody");
60
56
  }
61
57
  } catch (err) {
62
58
  throw new Error(
@@ -83,11 +79,7 @@ export class BuildPluginBuilder implements PluginBuilder {
83
79
  try {
84
80
  const result = cb(context);
85
81
  if (result) {
86
- if (Array.isArray(result)) {
87
- items.push(...result);
88
- } else {
89
- items.push(result);
90
- }
82
+ this.collectItems(items, result, "injectHead");
91
83
  }
92
84
  } catch (err) {
93
85
  throw new Error(
@@ -373,6 +365,8 @@ export class BuildPluginBuilder implements PluginBuilder {
373
365
  * Each callback receives the **previous** callback's return value (or the
374
366
  * original frontmatter for the first). Callbacks that return `undefined` or
375
367
  * `null` pass the current value through unchanged.
368
+ * Callbacks that return a non-object (string, number, array) are skipped
369
+ * with a console warning — only plain objects are accepted.
376
370
  * Errors inside individual callbacks are caught and logged — the current
377
371
  * frontmatter passes through unchanged for that step.
378
372
  *
@@ -389,7 +383,13 @@ export class BuildPluginBuilder implements PluginBuilder {
389
383
  try {
390
384
  const next = await this._transformFrontmatter[i](result, context);
391
385
  if (next !== undefined && next !== null) {
392
- result = next;
386
+ if (typeof next === "object" && !Array.isArray(next)) {
387
+ result = next;
388
+ } else {
389
+ console.warn(
390
+ `[plugin] transformFrontmatter callback #${i + 1} returned invalid type (expected a plain object), skipping`
391
+ );
392
+ }
393
393
  }
394
394
  } catch (err) {
395
395
  console.error(
@@ -430,6 +430,10 @@ export class BuildPluginBuilder implements PluginBuilder {
430
430
  * Callbacks are chained in a waterfall: the return value of one is passed
431
431
  * as input to the next. Return `undefined` to pass through unchanged.
432
432
  *
433
+ * **Note:** Only plain objects are accepted as return values. Returning
434
+ * a string, number, or array will be silently skipped with a warning.
435
+ * Plugin authors should validate their return values before returning.
436
+ *
433
437
  * @param callback - Receives frontmatter object and page context.
434
438
  *
435
439
  * @example
@@ -462,4 +466,28 @@ export class BuildPluginBuilder implements PluginBuilder {
462
466
  transformHtml(callback: (html: string, context: PageContext) => Awaitable<string>): void {
463
467
  this._transformHtml.push(callback);
464
468
  }
469
+
470
+ /**
471
+ * Collect items from a callback result, filtering only valid strings.
472
+ * Non-string items and unexpected types are logged as warnings.
473
+ */
474
+ private collectItems(items: string[], result: string | string[], hookName: string): void {
475
+ if (Array.isArray(result)) {
476
+ for (const item of result) {
477
+ if (typeof item === "string") {
478
+ items.push(item);
479
+ } else {
480
+ console.warn(
481
+ `[plugin] ${hookName} callback returned non-string item (got ${typeof item}), skipping`
482
+ );
483
+ }
484
+ }
485
+ } else if (typeof result === "string") {
486
+ items.push(result);
487
+ } else {
488
+ console.warn(
489
+ `[plugin] ${hookName} callback returned unexpected type (got ${typeof result}), expected string or string[], skipping`
490
+ );
491
+ }
492
+ }
465
493
  }
@@ -167,6 +167,9 @@ export interface PluginBuilder {
167
167
  * Register a callback that returns HTML strings to inject inside `<head>`.
168
168
  * Results from all plugins are merged and deduplicated.
169
169
  *
170
+ * ⚠️ Sanitize any user-controlled or external data before injecting.
171
+ * Plugin-provided strings are injected raw into the final HTML.
172
+ *
170
173
  * Use for: analytics snippets, meta tags, stylesheet links.
171
174
  *
172
175
  * @param callback - Returns a single HTML string or an array. Called once per page.
@@ -182,6 +185,9 @@ export interface PluginBuilder {
182
185
  * Register a callback that returns HTML strings to inject before `</body>`.
183
186
  * Results from all plugins are merged and deduplicated.
184
187
  *
188
+ * ⚠️ Sanitize any user-controlled or external data before injecting.
189
+ * Plugin-provided strings are injected raw into the final HTML.
190
+ *
185
191
  * Use for: chat widgets, live-script loaders, deferred scripts.
186
192
  *
187
193
  * @param callback - Returns a single HTML string or an array. Called once per page.
@@ -121,8 +121,12 @@ async function renderDocsServerPage(
121
121
  pathname: string,
122
122
  state: ServerState
123
123
  ): Promise<Response> {
124
- const title = (doc.frontmatter.title as string) || slug.join("/") || "Docs";
125
- const description = (doc.frontmatter.description as string) || "";
124
+ const title =
125
+ (typeof doc.frontmatter.title === "string" ? doc.frontmatter.title : "") ||
126
+ slug.join("/") ||
127
+ "Docs";
128
+ const description =
129
+ typeof doc.frontmatter.description === "string" ? doc.frontmatter.description : "";
126
130
 
127
131
  const page = React.createElement(
128
132
  DocsLayout,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/flame",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,9 +53,9 @@
53
53
  "react-dom": "^19.2.7",
54
54
  "unified": "^11.0.0",
55
55
  "@docubook/core": "^1.7.2",
56
- "@docubook/mdx-content": "^3.2.2",
57
56
  "@docubook/themes-colors": "^0.10.2",
58
- "@docubook/ui-react": "^0.1.4"
57
+ "@docubook/ui-react": "^0.1.4",
58
+ "@docubook/mdx-content": "^3.2.2"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@sentry/bun": "^10.0.0"