@zudojs/docs 0.0.1 → 1.0.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.
Files changed (126) hide show
  1. package/README.md +32 -9
  2. package/dist/docsTypes/index.d.ts +1 -1
  3. package/dist/document/documentBuilder.core.d.ts +15 -5
  4. package/dist/document/documentBuilder.core.js +27 -15
  5. package/dist/document/index.d.ts +1 -1
  6. package/dist/errors/index.d.ts +16 -0
  7. package/dist/errors/index.js +18 -0
  8. package/dist/examples/examples.core.d.ts +4 -0
  9. package/dist/examples/examples.core.js +16 -7
  10. package/dist/frontmatter/frontmatter.core.d.ts +1 -1
  11. package/dist/frontmatter/frontmatter.parser.d.ts +15 -2
  12. package/dist/frontmatter/frontmatter.parser.js +202 -39
  13. package/dist/frontmatter/frontmatter.serializer.d.ts +13 -1
  14. package/dist/frontmatter/frontmatter.serializer.js +89 -4
  15. package/dist/frontmatter/frontmatter.types.d.ts +10 -1
  16. package/dist/frontmatter/index.d.ts +1 -1
  17. package/dist/generator/generator.types.d.ts +18 -0
  18. package/dist/generator/generatorJson.core.d.ts +8 -3
  19. package/dist/generator/generatorJson.core.js +16 -4
  20. package/dist/generator/generatorMarkdown.core.d.ts +4 -0
  21. package/dist/generator/generatorMarkdown.core.js +32 -13
  22. package/dist/generator/generatorMarkdownNodes.d.ts +20 -0
  23. package/dist/generator/generatorMarkdownNodes.js +87 -17
  24. package/dist/generator/index.d.ts +2 -2
  25. package/dist/generator/index.js +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +2 -0
  28. package/dist/navigation/index.d.ts +1 -1
  29. package/dist/navigation/index.js +1 -1
  30. package/dist/navigation/navigation.core.d.ts +25 -6
  31. package/dist/navigation/navigation.core.js +93 -25
  32. package/dist/registry/index.d.ts +2 -1
  33. package/dist/registry/index.js +1 -1
  34. package/dist/registry/registry.core.d.ts +29 -3
  35. package/dist/registry/registry.core.js +32 -6
  36. package/dist/utils/index.d.ts +2 -1
  37. package/dist/utils/index.js +2 -1
  38. package/dist/utils/utils.freeze.d.ts +16 -0
  39. package/dist/utils/utils.freeze.js +53 -0
  40. package/dist/utils/utils.helper.d.ts +27 -4
  41. package/dist/utils/utils.helper.js +108 -21
  42. package/dist/validator/index.d.ts +4 -1
  43. package/dist/validator/index.js +2 -1
  44. package/dist/validator/validator.types.d.ts +8 -0
  45. package/dist/validator/validator.types.js +10 -1
  46. package/dist/validator/validatorAll/validatorAll.core.d.ts +11 -2
  47. package/dist/validator/validatorAll/validatorAll.core.js +19 -7
  48. package/dist/validator/validatorAll/validatorNavigation.core.d.ts +8 -1
  49. package/dist/validator/validatorAll/validatorNavigation.core.js +64 -10
  50. package/dist/validator/validatorDocument.core.d.ts +3 -2
  51. package/dist/validator/validatorDocument.core.js +197 -15
  52. package/dist/validator/validatorDuplicates.core.d.ts +1 -1
  53. package/dist/validator/validatorDuplicates.core.js +4 -6
  54. package/dist/validator/validatorLinks.core.d.ts +22 -4
  55. package/dist/validator/validatorLinks.core.js +80 -44
  56. package/package.json +18 -9
  57. package/dist/docsTypes/documentationContent.d.ts.map +0 -1
  58. package/dist/docsTypes/documentationContent.js.map +0 -1
  59. package/dist/docsTypes/documentationDocument.d.ts.map +0 -1
  60. package/dist/docsTypes/documentationDocument.js.map +0 -1
  61. package/dist/docsTypes/documentationMetadata.d.ts.map +0 -1
  62. package/dist/docsTypes/documentationMetadata.js.map +0 -1
  63. package/dist/docsTypes/documentationNavigation.d.ts.map +0 -1
  64. package/dist/docsTypes/documentationNavigation.js.map +0 -1
  65. package/dist/docsTypes/documentationProvider.d.ts.map +0 -1
  66. package/dist/docsTypes/documentationProvider.js.map +0 -1
  67. package/dist/docsTypes/index.d.ts.map +0 -1
  68. package/dist/docsTypes/index.js.map +0 -1
  69. package/dist/document/documentBuilder.core.d.ts.map +0 -1
  70. package/dist/document/documentBuilder.core.js.map +0 -1
  71. package/dist/document/index.d.ts.map +0 -1
  72. package/dist/document/index.js.map +0 -1
  73. package/dist/examples/examples.core.d.ts.map +0 -1
  74. package/dist/examples/examples.core.js.map +0 -1
  75. package/dist/examples/index.d.ts.map +0 -1
  76. package/dist/examples/index.js.map +0 -1
  77. package/dist/frontmatter/frontmatter.core.d.ts.map +0 -1
  78. package/dist/frontmatter/frontmatter.core.js.map +0 -1
  79. package/dist/frontmatter/frontmatter.parser.d.ts.map +0 -1
  80. package/dist/frontmatter/frontmatter.parser.js.map +0 -1
  81. package/dist/frontmatter/frontmatter.serializer.d.ts.map +0 -1
  82. package/dist/frontmatter/frontmatter.serializer.js.map +0 -1
  83. package/dist/frontmatter/frontmatter.types.d.ts.map +0 -1
  84. package/dist/frontmatter/frontmatter.types.js.map +0 -1
  85. package/dist/frontmatter/index.d.ts.map +0 -1
  86. package/dist/frontmatter/index.js.map +0 -1
  87. package/dist/generator/generator.types.d.ts.map +0 -1
  88. package/dist/generator/generator.types.js.map +0 -1
  89. package/dist/generator/generatorJson.core.d.ts.map +0 -1
  90. package/dist/generator/generatorJson.core.js.map +0 -1
  91. package/dist/generator/generatorMarkdown.core.d.ts.map +0 -1
  92. package/dist/generator/generatorMarkdown.core.js.map +0 -1
  93. package/dist/generator/generatorMarkdownNodes.d.ts.map +0 -1
  94. package/dist/generator/generatorMarkdownNodes.js.map +0 -1
  95. package/dist/generator/index.d.ts.map +0 -1
  96. package/dist/generator/index.js.map +0 -1
  97. package/dist/index.d.ts.map +0 -1
  98. package/dist/index.js.map +0 -1
  99. package/dist/navigation/index.d.ts.map +0 -1
  100. package/dist/navigation/index.js.map +0 -1
  101. package/dist/navigation/navigation.core.d.ts.map +0 -1
  102. package/dist/navigation/navigation.core.js.map +0 -1
  103. package/dist/registry/index.d.ts.map +0 -1
  104. package/dist/registry/index.js.map +0 -1
  105. package/dist/registry/registry.core.d.ts.map +0 -1
  106. package/dist/registry/registry.core.js.map +0 -1
  107. package/dist/utils/index.d.ts.map +0 -1
  108. package/dist/utils/index.js.map +0 -1
  109. package/dist/utils/utils.helper.d.ts.map +0 -1
  110. package/dist/utils/utils.helper.js.map +0 -1
  111. package/dist/validator/index.d.ts.map +0 -1
  112. package/dist/validator/index.js.map +0 -1
  113. package/dist/validator/validator.types.d.ts.map +0 -1
  114. package/dist/validator/validator.types.js.map +0 -1
  115. package/dist/validator/validatorAll/index.d.ts.map +0 -1
  116. package/dist/validator/validatorAll/index.js.map +0 -1
  117. package/dist/validator/validatorAll/validatorAll.core.d.ts.map +0 -1
  118. package/dist/validator/validatorAll/validatorAll.core.js.map +0 -1
  119. package/dist/validator/validatorAll/validatorNavigation.core.d.ts.map +0 -1
  120. package/dist/validator/validatorAll/validatorNavigation.core.js.map +0 -1
  121. package/dist/validator/validatorDocument.core.d.ts.map +0 -1
  122. package/dist/validator/validatorDocument.core.js.map +0 -1
  123. package/dist/validator/validatorDuplicates.core.d.ts.map +0 -1
  124. package/dist/validator/validatorDuplicates.core.js.map +0 -1
  125. package/dist/validator/validatorLinks.core.d.ts.map +0 -1
  126. package/dist/validator/validatorLinks.core.js.map +0 -1
@@ -3,22 +3,36 @@
3
3
  *
4
4
  * Provides breadcrumb generation, tree flattening, and
5
5
  * link resolution for documentation navigation structures.
6
+ *
7
+ * Every walker tolerates shared or cyclic nodes (a node is visited at
8
+ * most once) and stops at `MAX_NAVIGATION_DEPTH`, so malformed trees
9
+ * never overflow the stack.
6
10
  */
11
+ /** Maximum nesting depth any walker will descend. */
12
+ export const MAX_NAVIGATION_DEPTH = 64;
7
13
  /**
8
14
  * Generates breadcrumbs for a given document ID
9
- * by walking the navigation tree.
15
+ * by walking the navigation tree. Intermediate section nodes keep
16
+ * their `documentId` when they have one, so section landing pages
17
+ * stay linkable.
10
18
  */
11
19
  export function getBreadcrumbs(documentId, items) {
12
20
  const path = [];
13
- function walk(nodes) {
21
+ const seen = new WeakSet();
22
+ function walk(nodes, depth) {
23
+ if (depth > MAX_NAVIGATION_DEPTH)
24
+ return false;
14
25
  for (const node of nodes) {
26
+ if (seen.has(node))
27
+ continue;
28
+ seen.add(node);
15
29
  if (node.documentId === documentId) {
16
- path.push({ title: node.title, documentId: node.documentId });
30
+ path.push(toBreadcrumb(node));
17
31
  return true;
18
32
  }
19
33
  if (node.children) {
20
- path.push({ title: node.title });
21
- if (walk(node.children)) {
34
+ path.push(toBreadcrumb(node));
35
+ if (walk(node.children, depth + 1)) {
22
36
  return true;
23
37
  }
24
38
  path.pop();
@@ -26,60 +40,114 @@ export function getBreadcrumbs(documentId, items) {
26
40
  }
27
41
  return false;
28
42
  }
29
- walk(items);
43
+ walk(items, 0);
30
44
  return Object.freeze(path);
31
45
  }
46
+ function toBreadcrumb(node) {
47
+ return node.documentId === undefined
48
+ ? { title: node.title }
49
+ : { title: node.title, documentId: node.documentId };
50
+ }
32
51
  /**
33
52
  * Flattens a navigation tree into a list of all document IDs in order.
53
+ * A document referenced more than once appears once, at its first position.
34
54
  */
35
55
  export function flattenNavigation(items) {
36
56
  const result = [];
37
- function walk(nodes) {
57
+ const ids = new Set();
58
+ const seen = new WeakSet();
59
+ function walk(nodes, depth) {
60
+ if (depth > MAX_NAVIGATION_DEPTH)
61
+ return;
38
62
  for (const node of nodes) {
39
- if (node.documentId) {
63
+ if (seen.has(node))
64
+ continue;
65
+ seen.add(node);
66
+ if (node.documentId && !ids.has(node.documentId)) {
67
+ ids.add(node.documentId);
40
68
  result.push(node.documentId);
41
69
  }
42
70
  if (node.children) {
43
- walk(node.children);
71
+ walk(node.children, depth + 1);
44
72
  }
45
73
  }
46
74
  }
47
- walk(items);
75
+ walk(items, 0);
48
76
  return Object.freeze(result);
49
77
  }
50
78
  /**
51
79
  * Finds a navigation item by document ID.
52
80
  */
53
81
  export function findNavigationItem(documentId, items) {
54
- for (const node of items) {
55
- if (node.documentId === documentId) {
56
- return node;
57
- }
58
- if (node.children) {
59
- const found = findNavigationItem(documentId, node.children);
60
- if (found)
61
- return found;
82
+ const seen = new WeakSet();
83
+ function walk(nodes, depth) {
84
+ if (depth > MAX_NAVIGATION_DEPTH)
85
+ return undefined;
86
+ for (const node of nodes) {
87
+ if (seen.has(node))
88
+ continue;
89
+ seen.add(node);
90
+ if (node.documentId === documentId) {
91
+ return node;
92
+ }
93
+ if (node.children) {
94
+ const found = walk(node.children, depth + 1);
95
+ if (found)
96
+ return found;
97
+ }
62
98
  }
99
+ return undefined;
63
100
  }
64
- return undefined;
101
+ return walk(items, 0);
65
102
  }
66
103
  /**
67
- * Gets sibling document IDs for a given document.
104
+ * Returns the list of document IDs at the same level as `documentId`,
105
+ * including the document itself, in navigation order.
68
106
  */
69
- export function getSiblings(documentId, items) {
70
- function walk(nodes) {
107
+ function getLevel(documentId, items) {
108
+ const seen = new WeakSet();
109
+ function walk(nodes, depth) {
110
+ if (depth > MAX_NAVIGATION_DEPTH)
111
+ return undefined;
71
112
  for (const node of nodes) {
113
+ if (seen.has(node))
114
+ continue;
115
+ seen.add(node);
72
116
  if (node.documentId === documentId) {
73
- return nodes.filter((n) => n.documentId).map((n) => n.documentId);
117
+ return nodes
118
+ .map((n) => n.documentId)
119
+ .filter((id) => typeof id === "string");
74
120
  }
75
121
  if (node.children) {
76
- const result = walk(node.children);
122
+ const result = walk(node.children, depth + 1);
77
123
  if (result)
78
124
  return result;
79
125
  }
80
126
  }
81
127
  return undefined;
82
128
  }
83
- return Object.freeze(walk(items) ?? []);
129
+ return walk(items, 0) ?? [];
130
+ }
131
+ /**
132
+ * Gets sibling document IDs for a given document (the other documents
133
+ * at the same navigation level, excluding the document itself).
134
+ */
135
+ export function getSiblings(documentId, items) {
136
+ return Object.freeze(getLevel(documentId, items).filter((id) => id !== documentId));
137
+ }
138
+ /**
139
+ * Gets the previous and next document IDs at the same navigation level.
140
+ */
141
+ export function getAdjacent(documentId, items) {
142
+ const level = getLevel(documentId, items);
143
+ const index = level.indexOf(documentId);
144
+ if (index === -1)
145
+ return Object.freeze({});
146
+ const previous = level[index - 1];
147
+ const next = level[index + 1];
148
+ return Object.freeze({
149
+ ...(previous !== undefined ? { previous } : {}),
150
+ ...(next !== undefined ? { next } : {}),
151
+ });
84
152
  }
85
153
  //# sourceMappingURL=navigation.core.js.map
@@ -3,5 +3,6 @@
3
3
  *
4
4
  * Document registry for storing, retrieving, and filtering documentation.
5
5
  */
6
- export { DocumentRegistry, createDocumentRegistry } from "./registry.core.js";
6
+ export { DocumentRegistry, createDocumentRegistry, matchesVisibility, } from "./registry.core.js";
7
+ export type { DocumentVisibilityFilter, GetAllOptions, } from "./registry.core.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -3,5 +3,5 @@
3
3
  *
4
4
  * Document registry for storing, retrieving, and filtering documentation.
5
5
  */
6
- export { DocumentRegistry, createDocumentRegistry } from "./registry.core.js";
6
+ export { DocumentRegistry, createDocumentRegistry, matchesVisibility, } from "./registry.core.js";
7
7
  //# sourceMappingURL=index.js.map
@@ -5,13 +5,34 @@
5
5
  * documentation documents. Prevents duplicate IDs.
6
6
  */
7
7
  import type { DocumentationDocument, DocumentationProvider } from "../docsTypes/index.js";
8
+ /** Visibility filter accepted by `getAll` and the generators. */
9
+ export type DocumentVisibilityFilter = "SERVER" | "CLIENT" | "ALL";
10
+ /** Options for `DocumentRegistry.getAll`. */
11
+ export interface GetAllOptions {
12
+ /**
13
+ * Which documents to return. `"CLIENT"` returns documents whose
14
+ * `visibility` is `"CLIENT"` or unset; `"SERVER"` returns only
15
+ * server-only documents; `"ALL"` (default) returns everything.
16
+ */
17
+ readonly visibility?: DocumentVisibilityFilter;
18
+ }
19
+ /**
20
+ * Returns true when `document` should be included for the given filter.
21
+ */
22
+ export declare function matchesVisibility(document: DocumentationDocument, filter?: DocumentVisibilityFilter): boolean;
8
23
  /**
9
24
  * Registry for managing documentation documents.
25
+ *
26
+ * Registered documents are stored as deep-frozen copies, so the
27
+ * caller's object is never mutated and later changes to it do not
28
+ * leak into the registry.
10
29
  */
11
30
  export declare class DocumentRegistry implements DocumentationProvider {
12
31
  private readonly documents;
13
32
  /**
14
- * Registers a document. Throws if the ID is already registered.
33
+ * Registers a document.
34
+ *
35
+ * @throws {DuplicateDocumentError} if the ID is already registered.
15
36
  */
16
37
  register(document: DocumentationDocument): void;
17
38
  /**
@@ -23,9 +44,9 @@ export declare class DocumentRegistry implements DocumentationProvider {
23
44
  */
24
45
  get(id: string): DocumentationDocument | undefined;
25
46
  /**
26
- * Returns all registered documents.
47
+ * Returns all registered documents, optionally filtered by visibility.
27
48
  */
28
- getAll(): readonly DocumentationDocument[];
49
+ getAll(options?: GetAllOptions): readonly DocumentationDocument[];
29
50
  /**
30
51
  * Returns the number of registered documents.
31
52
  */
@@ -46,6 +67,11 @@ export declare class DocumentRegistry implements DocumentationProvider {
46
67
  * Returns all document IDs.
47
68
  */
48
69
  ids(): readonly string[];
70
+ /**
71
+ * Returns the registered IDs as a set, suitable for `validateLinks`
72
+ * and `validateNavigation`.
73
+ */
74
+ idSet(): ReadonlySet<string>;
49
75
  /**
50
76
  * Filters documents by category.
51
77
  */
@@ -4,19 +4,37 @@
4
4
  * Provides registration, retrieval, and iteration over
5
5
  * documentation documents. Prevents duplicate IDs.
6
6
  */
7
+ import { DuplicateDocumentError } from "@zudojs/errors";
8
+ import { deepFreezeClone } from "../utils/utils.freeze.js";
9
+ /**
10
+ * Returns true when `document` should be included for the given filter.
11
+ */
12
+ export function matchesVisibility(document, filter = "ALL") {
13
+ if (filter === "ALL")
14
+ return true;
15
+ if (filter === "SERVER")
16
+ return document.visibility === "SERVER";
17
+ return document.visibility !== "SERVER";
18
+ }
7
19
  /**
8
20
  * Registry for managing documentation documents.
21
+ *
22
+ * Registered documents are stored as deep-frozen copies, so the
23
+ * caller's object is never mutated and later changes to it do not
24
+ * leak into the registry.
9
25
  */
10
26
  export class DocumentRegistry {
11
27
  documents = new Map();
12
28
  /**
13
- * Registers a document. Throws if the ID is already registered.
29
+ * Registers a document.
30
+ *
31
+ * @throws {DuplicateDocumentError} if the ID is already registered.
14
32
  */
15
33
  register(document) {
16
34
  if (this.documents.has(document.id)) {
17
- throw new Error(`Duplicate document ID: "${document.id}".`);
35
+ throw new DuplicateDocumentError(document.id);
18
36
  }
19
- this.documents.set(document.id, Object.freeze(document));
37
+ this.documents.set(document.id, deepFreezeClone(document));
20
38
  }
21
39
  /**
22
40
  * Registers multiple documents.
@@ -33,10 +51,11 @@ export class DocumentRegistry {
33
51
  return this.documents.get(id);
34
52
  }
35
53
  /**
36
- * Returns all registered documents.
54
+ * Returns all registered documents, optionally filtered by visibility.
37
55
  */
38
- getAll() {
39
- return Object.freeze([...this.documents.values()]);
56
+ getAll(options = {}) {
57
+ const filter = options.visibility ?? "ALL";
58
+ return Object.freeze([...this.documents.values()].filter((doc) => matchesVisibility(doc, filter)));
40
59
  }
41
60
  /**
42
61
  * Returns the number of registered documents.
@@ -68,6 +87,13 @@ export class DocumentRegistry {
68
87
  ids() {
69
88
  return Object.freeze([...this.documents.keys()]);
70
89
  }
90
+ /**
91
+ * Returns the registered IDs as a set, suitable for `validateLinks`
92
+ * and `validateNavigation`.
93
+ */
94
+ idSet() {
95
+ return new Set(this.documents.keys());
96
+ }
71
97
  /**
72
98
  * Filters documents by category.
73
99
  */
@@ -3,5 +3,6 @@
3
3
  *
4
4
  * Utility helpers for document ID normalization, link resolution, and markdown parsing.
5
5
  */
6
- export { normalizeDocumentId, documentIdFromPath, resolveDocumentLink, extractTitleFromMarkdown, extractHeadings, stripMarkdown, } from "./utils.helper.js";
6
+ export { isValidDocumentId, normalizeDocumentId, documentIdFromPath, resolveDocumentLink, stripLinkDecorations, stripFencedCodeBlocks, extractTitleFromMarkdown, extractHeadings, stripMarkdown, } from "./utils.helper.js";
7
+ export { deepFreeze, deepFreezeClone } from "./utils.freeze.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -3,5 +3,6 @@
3
3
  *
4
4
  * Utility helpers for document ID normalization, link resolution, and markdown parsing.
5
5
  */
6
- export { normalizeDocumentId, documentIdFromPath, resolveDocumentLink, extractTitleFromMarkdown, extractHeadings, stripMarkdown, } from "./utils.helper.js";
6
+ export { isValidDocumentId, normalizeDocumentId, documentIdFromPath, resolveDocumentLink, stripLinkDecorations, stripFencedCodeBlocks, extractTitleFromMarkdown, extractHeadings, stripMarkdown, } from "./utils.helper.js";
7
+ export { deepFreeze, deepFreezeClone } from "./utils.freeze.js";
7
8
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Deep-freeze helpers for immutable value objects.
3
+ */
4
+ /**
5
+ * Recursively freezes a value in place. Cycles are tolerated.
6
+ * Only plain objects and arrays are traversed; class instances such
7
+ * as `Date` are frozen but not walked.
8
+ */
9
+ export declare function deepFreeze<T>(value: T, seen?: WeakSet<object>): T;
10
+ /**
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).
14
+ */
15
+ export declare function deepFreezeClone<T>(value: T): T;
16
+ //# sourceMappingURL=utils.freeze.d.ts.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Deep-freeze helpers for immutable value objects.
3
+ */
4
+ /**
5
+ * Recursively freezes a value in place. Cycles are tolerated.
6
+ * Only plain objects and arrays are traversed; class instances such
7
+ * as `Date` are frozen but not walked.
8
+ */
9
+ export function deepFreeze(value, seen = new WeakSet()) {
10
+ if (typeof value !== "object" || value === null)
11
+ return value;
12
+ if (seen.has(value))
13
+ return value;
14
+ seen.add(value);
15
+ if (Array.isArray(value)) {
16
+ for (const item of value)
17
+ deepFreeze(item, seen);
18
+ }
19
+ else if (isPlainObject(value)) {
20
+ for (const item of Object.values(value))
21
+ deepFreeze(item, seen);
22
+ }
23
+ return Object.freeze(value);
24
+ }
25
+ /**
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).
29
+ */
30
+ export function deepFreezeClone(value) {
31
+ let copy;
32
+ try {
33
+ copy = structuredClone(value);
34
+ }
35
+ catch {
36
+ copy = shallowCopy(value);
37
+ }
38
+ return deepFreeze(copy);
39
+ }
40
+ function shallowCopy(value) {
41
+ if (Array.isArray(value))
42
+ return [...value];
43
+ if (isPlainObject(value))
44
+ return { ...value };
45
+ return value;
46
+ }
47
+ function isPlainObject(value) {
48
+ if (typeof value !== "object" || value === null)
49
+ return false;
50
+ const proto = Object.getPrototypeOf(value);
51
+ return proto === Object.prototype || proto === null;
52
+ }
53
+ //# sourceMappingURL=utils.freeze.js.map
@@ -1,27 +1,50 @@
1
1
  /**
2
2
  * Utility helpers for the documentation package.
3
3
  */
4
+ /**
5
+ * Returns true when `id` is a stable dot-separated identifier such as
6
+ * `guides.http.routing`: non-empty segments of letters, digits, `_`
7
+ * and `-`, with no whitespace or empty segments.
8
+ */
9
+ export declare function isValidDocumentId(id: string): boolean;
4
10
  /**
5
11
  * Normalizes a document ID to a consistent format.
6
- * Strips leading/trailing dots, collapses consecutive dots.
12
+ * Trims whitespace, collapses consecutive dots and strips leading/trailing dots.
7
13
  */
8
14
  export declare function normalizeDocumentId(id: string): string;
9
15
  /**
10
16
  * Generates a document ID from a file path.
11
- * Converts path separators to dots and removes extension.
17
+ * Converts path separators to dots and removes the extension of the
18
+ * final segment only (`guides/v1.2/routing.md` → `guides.v1.2.routing`).
19
+ * Returns an empty string when the path has no usable segment
20
+ * (e.g. `.gitignore`).
12
21
  */
13
22
  export declare function documentIdFromPath(path: string): string;
14
23
  /**
15
24
  * Resolves a relative document link against a base ID.
16
25
  * "guides.http" + "./routing" → "guides.http.routing"
26
+ *
27
+ * Fragments (`#section`), `.md`/`.mdx` extensions and Windows
28
+ * separators are stripped before resolving.
17
29
  */
18
30
  export declare function resolveDocumentLink(baseId: string, link: string): string;
19
31
  /**
20
- * Extracts the title from markdown content (first heading).
32
+ * Removes a `#fragment`, a trailing `.md`/`.mdx` extension and
33
+ * converts `\` to `/` in a link target.
34
+ */
35
+ export declare function stripLinkDecorations(link: string): string;
36
+ /**
37
+ * Removes fenced code blocks (``` or ~~~) from markdown so that
38
+ * headings and links inside them are not interpreted.
39
+ */
40
+ export declare function stripFencedCodeBlocks(markdown: string): string;
41
+ /**
42
+ * Extracts the title from markdown content (first level-1 heading
43
+ * outside fenced code blocks).
21
44
  */
22
45
  export declare function extractTitleFromMarkdown(markdown: string): string | undefined;
23
46
  /**
24
- * Extracts headings from markdown content.
47
+ * Extracts headings from markdown content (outside fenced code blocks).
25
48
  */
26
49
  export declare function extractHeadings(markdown: string): readonly {
27
50
  level: number;
@@ -1,34 +1,64 @@
1
1
  /**
2
2
  * Utility helpers for the documentation package.
3
3
  */
4
+ /** Characters allowed in one segment of a document ID. */
5
+ const ID_SEGMENT = /^[A-Za-z0-9_-]+$/;
6
+ /**
7
+ * Returns true when `id` is a stable dot-separated identifier such as
8
+ * `guides.http.routing`: non-empty segments of letters, digits, `_`
9
+ * and `-`, with no whitespace or empty segments.
10
+ */
11
+ export function isValidDocumentId(id) {
12
+ if (typeof id !== "string" || id.length === 0)
13
+ return false;
14
+ return id.split(".").every((segment) => ID_SEGMENT.test(segment));
15
+ }
4
16
  /**
5
17
  * Normalizes a document ID to a consistent format.
6
- * Strips leading/trailing dots, collapses consecutive dots.
18
+ * Trims whitespace, collapses consecutive dots and strips leading/trailing dots.
7
19
  */
8
20
  export function normalizeDocumentId(id) {
9
- return id
21
+ return (id
22
+ .trim()
10
23
  .replace(/\.+/g, ".")
11
- .replace(/^\.|\.$/g, "")
12
- .trim();
24
+ // Runs are already collapsed, so at most one dot can sit at each end.
25
+ // Matching a single dot keeps this linear; `\.+$` would backtrack.
26
+ .replace(/^\.|\.$/g, ""));
13
27
  }
14
28
  /**
15
29
  * Generates a document ID from a file path.
16
- * Converts path separators to dots and removes extension.
30
+ * Converts path separators to dots and removes the extension of the
31
+ * final segment only (`guides/v1.2/routing.md` → `guides.v1.2.routing`).
32
+ * Returns an empty string when the path has no usable segment
33
+ * (e.g. `.gitignore`).
17
34
  */
18
35
  export function documentIdFromPath(path) {
19
- return normalizeDocumentId(path.replace(/\.[^.]+$/, "").replace(/[/\\]/g, "."));
36
+ const normalizedPath = path.replace(/\\/g, "/").replace(/^\.\//, "");
37
+ const segments = normalizedPath.split("/").filter((s) => s.length > 0);
38
+ if (segments.length === 0)
39
+ return "";
40
+ const last = segments[segments.length - 1] ?? "";
41
+ const stem = last.replace(/\.[^.]+$/, "");
42
+ if (stem.length === 0)
43
+ return "";
44
+ segments[segments.length - 1] = stem;
45
+ return normalizeDocumentId(segments.join("."));
20
46
  }
21
47
  /**
22
48
  * Resolves a relative document link against a base ID.
23
49
  * "guides.http" + "./routing" → "guides.http.routing"
50
+ *
51
+ * Fragments (`#section`), `.md`/`.mdx` extensions and Windows
52
+ * separators are stripped before resolving.
24
53
  */
25
54
  export function resolveDocumentLink(baseId, link) {
26
- if (link.startsWith("/")) {
27
- return normalizeDocumentId(link.slice(1));
55
+ const cleaned = stripLinkDecorations(link);
56
+ if (cleaned.startsWith("/")) {
57
+ return normalizeDocumentId(cleaned.slice(1).replace(/\//g, "."));
28
58
  }
29
- const baseParts = baseId.split(".");
59
+ const baseParts = baseId.split(".").filter((p) => p.length > 0);
30
60
  baseParts.pop();
31
- const linkParts = link.split("/");
61
+ const linkParts = cleaned.split("/");
32
62
  for (const part of linkParts) {
33
63
  if (part === "..") {
34
64
  baseParts.pop();
@@ -40,43 +70,100 @@ export function resolveDocumentLink(baseId, link) {
40
70
  return normalizeDocumentId(baseParts.join("."));
41
71
  }
42
72
  /**
43
- * Extracts the title from markdown content (first heading).
73
+ * Removes a `#fragment`, a trailing `.md`/`.mdx` extension and
74
+ * converts `\` to `/` in a link target.
75
+ */
76
+ export function stripLinkDecorations(link) {
77
+ const withoutFragment = link.split("#")[0] ?? "";
78
+ return withoutFragment.replace(/\\/g, "/").replace(/\.mdx?$/i, "");
79
+ }
80
+ /**
81
+ * Removes fenced code blocks (``` or ~~~) from markdown so that
82
+ * headings and links inside them are not interpreted.
83
+ */
84
+ export function stripFencedCodeBlocks(markdown) {
85
+ return markdown.replace(/^(`{3,}|~{3,})[^\n]*\n[\s\S]*?^\1[^\n]*$/gm, "");
86
+ }
87
+ /**
88
+ * Extracts the title from markdown content (first level-1 heading
89
+ * outside fenced code blocks).
44
90
  */
45
91
  export function extractTitleFromMarkdown(markdown) {
46
- const match = markdown.match(/^# ([^\n]+)$/m);
47
- return match?.[1]?.trim();
92
+ const match = stripFencedCodeBlocks(markdown).match(/^# ([^\n]+)$/m);
93
+ return match?.[1] ? cleanHeadingText(match[1]) : undefined;
48
94
  }
49
95
  /**
50
- * Extracts headings from markdown content.
96
+ * Extracts headings from markdown content (outside fenced code blocks).
51
97
  */
52
98
  export function extractHeadings(markdown) {
53
99
  const results = [];
54
100
  const pattern = /^(#{1,6}) ([^\n]+)$/gm;
101
+ const source = stripFencedCodeBlocks(markdown);
55
102
  let match;
56
- while ((match = pattern.exec(markdown)) !== null) {
103
+ while ((match = pattern.exec(source)) !== null) {
57
104
  const hashes = match[1];
58
105
  const headingText = match[2];
59
106
  if (hashes && headingText) {
60
107
  results.push({
61
108
  level: hashes.length,
62
- text: headingText.trim(),
109
+ text: cleanHeadingText(headingText),
63
110
  });
64
111
  }
65
112
  }
66
113
  return Object.freeze(results);
67
114
  }
115
+ /** Trims a heading and removes ATX closing hashes (`# Title #`). */
116
+ function cleanHeadingText(text) {
117
+ return text.replace(/\s+#+\s*$/, "").trim();
118
+ }
119
+ const HTML_TAG = /<\/?[a-zA-Z][^>\n]*>/g;
120
+ /**
121
+ * Removes HTML tags until the result stops changing. A single pass is not
122
+ * enough: stripping the inner tag of `<<b>b>x` re-forms `<b>x`, so a lone pass
123
+ * can leave a tag behind in text that is later rendered as HTML.
124
+ */
125
+ function stripHtmlTags(text) {
126
+ let current = text;
127
+ let previous;
128
+ do {
129
+ previous = current;
130
+ current = current.replace(HTML_TAG, "");
131
+ } while (current !== previous);
132
+ return current;
133
+ }
68
134
  /**
69
135
  * Strips markdown formatting to plain text.
70
136
  */
71
137
  export function stripMarkdown(markdown) {
72
- return markdown
138
+ return (stripHtmlTags(
139
+ // fenced code blocks: keep the code, drop the fences
140
+ markdown.replace(/^(`{3,}|~{3,})[^\n]*\n([\s\S]*?)^\1[^\n]*$/gm, "$2"))
141
+ // images before links
142
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
143
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
144
+ // headings
73
145
  .replace(/^#{1,6}\s+/gm, "")
74
- .replace(/\*\*([^*]+)\*\*/g, "$1")
75
- .replace(/\*([^*]+)\*/g, "$1")
146
+ .replace(/\s+#+\s*$/gm, "")
147
+ // emphasis (bold before italic, non-greedy)
148
+ .replace(/\*\*(.+?)\*\*/g, "$1")
149
+ .replace(/__(.+?)__/g, "$1")
150
+ .replace(/\*(.+?)\*/g, "$1")
151
+ .replace(/(^|[^\w])_(.+?)_(?=[^\w]|$)/g, "$1$2")
152
+ .replace(/~~(.+?)~~/g, "$1")
153
+ // inline code
76
154
  .replace(/`([^`]+)`/g, "$1")
77
- .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
155
+ // blockquotes, list markers
156
+ .replace(/^\s*>\s?/gm, "")
78
157
  .replace(/^\s*[-*+]\s+/gm, "")
79
158
  .replace(/^\s*\d+\.\s+/gm, "")
80
- .trim();
159
+ // tables: drop separator rows, unpipe cells
160
+ .replace(/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/gm, "")
161
+ .replace(/^\s*\|/gm, "")
162
+ .replace(/\|\s*$/gm, "")
163
+ .replace(/\s*\|\s*/g, " ")
164
+ // horizontal rules
165
+ .replace(/^\s*([-*_]\s*){3,}$/gm, "")
166
+ .replace(/\n{3,}/g, "\n\n")
167
+ .trim());
81
168
  }
82
169
  //# sourceMappingURL=utils.helper.js.map
@@ -5,7 +5,10 @@
5
5
  */
6
6
  export { validateDocument } from "./validatorDocument.core.js";
7
7
  export { validateNoDuplicateIds } from "./validatorDuplicates.core.js";
8
- export { validateLinks } from "./validatorLinks.core.js";
8
+ export { validateLinks, DEFAULT_MAX_LINK_SCAN_LENGTH, } from "./validatorLinks.core.js";
9
9
  export { validateAll, validateNavigation } from "./validatorAll/index.js";
10
+ export { toValidationResult } from "./validator.types.js";
10
11
  export type { ValidationIssue, ValidationResult } from "./validator.types.js";
12
+ export type { ValidateLinksOptions } from "./validatorLinks.core.js";
13
+ export type { ValidateAllOptions } from "./validatorAll/validatorAll.core.js";
11
14
  //# sourceMappingURL=index.d.ts.map