@zudojs/docs 1.0.0 → 1.0.1

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.
@@ -5,12 +5,12 @@
5
5
  * untrusted node content cannot break out of a table, code fence,
6
6
  * heading or link.
7
7
  */
8
- const CALLOUT_LABELS = {
9
- note: "NOTE",
10
- warning: "WARNING",
11
- tip: "TIP",
12
- danger: "DANGER",
13
- };
8
+ const CALLOUT_LABELS = new Map([
9
+ ["note", "NOTE"],
10
+ ["warning", "WARNING"],
11
+ ["tip", "TIP"],
12
+ ["danger", "DANGER"],
13
+ ]);
14
14
  /**
15
15
  * Converts a list of documentation nodes to markdown.
16
16
  *
@@ -65,7 +65,11 @@ export function nodesToMarkdown(nodes) {
65
65
  lines.push("");
66
66
  break;
67
67
  case "callout": {
68
- const label = CALLOUT_LABELS[node.kind] ?? singleLine(String(node.kind)).toUpperCase();
68
+ // A Map, not a plain-object lookup: a kind of "constructor" coming
69
+ // from untrusted JSON must not resolve to Object.prototype.constructor
70
+ // and print a function's source as the label.
71
+ const label = CALLOUT_LABELS.get(node.kind) ??
72
+ singleLine(String(node.kind)).toUpperCase();
69
73
  const [first = "", ...rest] = node.value.split(/\r?\n/);
70
74
  lines.push(`> **${label}:** ${first}`);
71
75
  for (const line of rest) {
@@ -9,8 +9,9 @@
9
9
  export declare function deepFreeze<T>(value: T, seen?: WeakSet<object>): T;
10
10
  /**
11
11
  * Returns a deep-frozen copy of `value`, leaving the caller's object
12
- * untouched. Falls back to freezing a shallow copy when the value
13
- * cannot be structurally cloned (e.g. contains functions).
12
+ * untouched. Falls back to a recursive copy of plain objects, arrays and
13
+ * Dates when the value cannot be structurally cloned (e.g. contains
14
+ * functions); those are kept by reference.
14
15
  */
15
16
  export declare function deepFreezeClone<T>(value: T): T;
16
17
  //# sourceMappingURL=utils.freeze.d.ts.map
@@ -24,8 +24,9 @@ export function deepFreeze(value, seen = new WeakSet()) {
24
24
  }
25
25
  /**
26
26
  * Returns a deep-frozen copy of `value`, leaving the caller's object
27
- * untouched. Falls back to freezing a shallow copy when the value
28
- * cannot be structurally cloned (e.g. contains functions).
27
+ * untouched. Falls back to a recursive copy of plain objects, arrays and
28
+ * Dates when the value cannot be structurally cloned (e.g. contains
29
+ * functions); those are kept by reference.
29
30
  */
30
31
  export function deepFreezeClone(value) {
31
32
  let copy;
@@ -33,15 +34,38 @@ export function deepFreezeClone(value) {
33
34
  copy = structuredClone(value);
34
35
  }
35
36
  catch {
36
- copy = shallowCopy(value);
37
+ copy = deepCopy(value, new WeakMap());
37
38
  }
38
39
  return deepFreeze(copy);
39
40
  }
40
- function shallowCopy(value) {
41
- if (Array.isArray(value))
42
- return [...value];
43
- if (isPlainObject(value))
44
- return { ...value };
41
+ /**
42
+ * Recursive fallback for values `structuredClone` rejects. A shallow copy
43
+ * was not enough: `deepFreeze` then walked into the caller's nested
44
+ * objects and froze them in place.
45
+ */
46
+ function deepCopy(value, seen) {
47
+ if (typeof value !== "object" || value === null)
48
+ return value;
49
+ if (seen.has(value))
50
+ return seen.get(value);
51
+ if (value instanceof Date) {
52
+ return new Date(value.getTime());
53
+ }
54
+ if (Array.isArray(value)) {
55
+ const copy = [];
56
+ seen.set(value, copy);
57
+ for (const item of value)
58
+ copy.push(deepCopy(item, seen));
59
+ return copy;
60
+ }
61
+ if (isPlainObject(value)) {
62
+ const copy = {};
63
+ seen.set(value, copy);
64
+ for (const [key, item] of Object.entries(value)) {
65
+ copy[key] = deepCopy(item, seen);
66
+ }
67
+ return copy;
68
+ }
45
69
  return value;
46
70
  }
47
71
  function isPlainObject(value) {
@@ -43,11 +43,14 @@ export function validateNavigation(items, registeredIds) {
43
43
  });
44
44
  continue;
45
45
  }
46
- if (visited.has(node))
47
- continue;
46
+ // The same node object mounted in two places is still the document
47
+ // appearing twice in the navigation, so the duplicate check runs on
48
+ // every visit; only the per-node checks and the descent are skipped
49
+ // for a node already seen.
50
+ const firstVisit = !visited.has(node);
48
51
  visited.add(node);
49
52
  if (node.documentId) {
50
- if (!registeredIds.has(node.documentId)) {
53
+ if (firstVisit && !registeredIds.has(node.documentId)) {
51
54
  issues.push({
52
55
  severity: "error",
53
56
  code: "NAVIGATION_UNKNOWN_DOCUMENT",
@@ -65,14 +68,14 @@ export function validateNavigation(items, registeredIds) {
65
68
  }
66
69
  seenIds.add(node.documentId);
67
70
  }
68
- else if (!node.children || node.children.length === 0) {
71
+ else if (firstVisit && (!node.children || node.children.length === 0)) {
69
72
  issues.push({
70
73
  severity: "warning",
71
74
  code: "NAVIGATION_EMPTY_ITEM",
72
75
  message: `Navigation item "${node.title}" has neither a documentId nor children.`,
73
76
  });
74
77
  }
75
- if (node.children) {
78
+ if (firstVisit && node.children) {
76
79
  walk(node.children, [...ancestors, node]);
77
80
  }
78
81
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/docs",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Documentation infrastructure with structured document model, registry, validation, navigation, and generation.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -11,13 +11,17 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@zudojs/errors": "1.0.0"
14
+ "@zudojs/errors": "1.0.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "typescript": "7.0.2",
18
18
  "vitest": "^4.1.11"
19
19
  },
20
20
  "license": "MIT",
21
+ "author": {
22
+ "name": "Oluwayemi Oyinlola",
23
+ "url": "https://github.com/oyinlola-tech"
24
+ },
21
25
  "type": "module",
22
26
  "files": [
23
27
  "dist",