@zudojs/docs 0.1.0 → 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.
- package/LICENSE +21 -0
- package/README.md +32 -9
- package/dist/docsTypes/index.d.ts +1 -1
- package/dist/document/documentBuilder.core.d.ts +15 -5
- package/dist/document/documentBuilder.core.js +27 -15
- package/dist/document/index.d.ts +1 -1
- package/dist/errors/index.d.ts +16 -0
- package/dist/errors/index.js +18 -0
- package/dist/examples/examples.core.d.ts +4 -0
- package/dist/examples/examples.core.js +16 -7
- package/dist/frontmatter/frontmatter.core.d.ts +1 -1
- package/dist/frontmatter/frontmatter.parser.d.ts +15 -2
- package/dist/frontmatter/frontmatter.parser.js +202 -39
- package/dist/frontmatter/frontmatter.serializer.d.ts +13 -1
- package/dist/frontmatter/frontmatter.serializer.js +89 -4
- package/dist/frontmatter/frontmatter.types.d.ts +10 -1
- package/dist/frontmatter/index.d.ts +1 -1
- package/dist/generator/generator.types.d.ts +18 -0
- package/dist/generator/generatorJson.core.d.ts +8 -3
- package/dist/generator/generatorJson.core.js +16 -4
- package/dist/generator/generatorMarkdown.core.d.ts +4 -0
- package/dist/generator/generatorMarkdown.core.js +32 -13
- package/dist/generator/generatorMarkdownNodes.d.ts +20 -0
- package/dist/generator/generatorMarkdownNodes.js +87 -17
- package/dist/generator/index.d.ts +2 -2
- package/dist/generator/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.js +1 -1
- package/dist/navigation/navigation.core.d.ts +25 -6
- package/dist/navigation/navigation.core.js +93 -25
- package/dist/registry/index.d.ts +2 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/registry.core.d.ts +29 -3
- package/dist/registry/registry.core.js +32 -6
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/utils.freeze.d.ts +16 -0
- package/dist/utils/utils.freeze.js +53 -0
- package/dist/utils/utils.helper.d.ts +27 -4
- package/dist/utils/utils.helper.js +108 -21
- package/dist/validator/index.d.ts +4 -1
- package/dist/validator/index.js +2 -1
- package/dist/validator/validator.types.d.ts +8 -0
- package/dist/validator/validator.types.js +10 -1
- package/dist/validator/validatorAll/validatorAll.core.d.ts +11 -2
- package/dist/validator/validatorAll/validatorAll.core.js +19 -7
- package/dist/validator/validatorAll/validatorNavigation.core.d.ts +8 -1
- package/dist/validator/validatorAll/validatorNavigation.core.js +64 -10
- package/dist/validator/validatorDocument.core.d.ts +3 -2
- package/dist/validator/validatorDocument.core.js +197 -15
- package/dist/validator/validatorDuplicates.core.d.ts +1 -1
- package/dist/validator/validatorDuplicates.core.js +4 -6
- package/dist/validator/validatorLinks.core.d.ts +22 -4
- package/dist/validator/validatorLinks.core.js +80 -44
- package/package.json +24 -15
- package/dist/docsTypes/documentationContent.d.ts.map +0 -1
- package/dist/docsTypes/documentationContent.js.map +0 -1
- package/dist/docsTypes/documentationDocument.d.ts.map +0 -1
- package/dist/docsTypes/documentationDocument.js.map +0 -1
- package/dist/docsTypes/documentationMetadata.d.ts.map +0 -1
- package/dist/docsTypes/documentationMetadata.js.map +0 -1
- package/dist/docsTypes/documentationNavigation.d.ts.map +0 -1
- package/dist/docsTypes/documentationNavigation.js.map +0 -1
- package/dist/docsTypes/documentationProvider.d.ts.map +0 -1
- package/dist/docsTypes/documentationProvider.js.map +0 -1
- package/dist/docsTypes/index.d.ts.map +0 -1
- package/dist/docsTypes/index.js.map +0 -1
- package/dist/document/documentBuilder.core.d.ts.map +0 -1
- package/dist/document/documentBuilder.core.js.map +0 -1
- package/dist/document/index.d.ts.map +0 -1
- package/dist/document/index.js.map +0 -1
- package/dist/examples/examples.core.d.ts.map +0 -1
- package/dist/examples/examples.core.js.map +0 -1
- package/dist/examples/index.d.ts.map +0 -1
- package/dist/examples/index.js.map +0 -1
- package/dist/frontmatter/frontmatter.core.d.ts.map +0 -1
- package/dist/frontmatter/frontmatter.core.js.map +0 -1
- package/dist/frontmatter/frontmatter.parser.d.ts.map +0 -1
- package/dist/frontmatter/frontmatter.parser.js.map +0 -1
- package/dist/frontmatter/frontmatter.serializer.d.ts.map +0 -1
- package/dist/frontmatter/frontmatter.serializer.js.map +0 -1
- package/dist/frontmatter/frontmatter.types.d.ts.map +0 -1
- package/dist/frontmatter/frontmatter.types.js.map +0 -1
- package/dist/frontmatter/index.d.ts.map +0 -1
- package/dist/frontmatter/index.js.map +0 -1
- package/dist/generator/generator.types.d.ts.map +0 -1
- package/dist/generator/generator.types.js.map +0 -1
- package/dist/generator/generatorJson.core.d.ts.map +0 -1
- package/dist/generator/generatorJson.core.js.map +0 -1
- package/dist/generator/generatorMarkdown.core.d.ts.map +0 -1
- package/dist/generator/generatorMarkdown.core.js.map +0 -1
- package/dist/generator/generatorMarkdownNodes.d.ts.map +0 -1
- package/dist/generator/generatorMarkdownNodes.js.map +0 -1
- package/dist/generator/index.d.ts.map +0 -1
- package/dist/generator/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/navigation/index.d.ts.map +0 -1
- package/dist/navigation/index.js.map +0 -1
- package/dist/navigation/navigation.core.d.ts.map +0 -1
- package/dist/navigation/navigation.core.js.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/registry.core.d.ts.map +0 -1
- package/dist/registry/registry.core.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/utils.helper.d.ts.map +0 -1
- package/dist/utils/utils.helper.js.map +0 -1
- package/dist/validator/index.d.ts.map +0 -1
- package/dist/validator/index.js.map +0 -1
- package/dist/validator/validator.types.d.ts.map +0 -1
- package/dist/validator/validator.types.js.map +0 -1
- package/dist/validator/validatorAll/index.d.ts.map +0 -1
- package/dist/validator/validatorAll/index.js.map +0 -1
- package/dist/validator/validatorAll/validatorAll.core.d.ts.map +0 -1
- package/dist/validator/validatorAll/validatorAll.core.js.map +0 -1
- package/dist/validator/validatorAll/validatorNavigation.core.d.ts.map +0 -1
- package/dist/validator/validatorAll/validatorNavigation.core.js.map +0 -1
- package/dist/validator/validatorDocument.core.d.ts.map +0 -1
- package/dist/validator/validatorDocument.core.js.map +0 -1
- package/dist/validator/validatorDuplicates.core.d.ts.map +0 -1
- package/dist/validator/validatorDuplicates.core.js.map +0 -1
- package/dist/validator/validatorLinks.core.d.ts.map +0 -1
- package/dist/validator/validatorLinks.core.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zudojs Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -11,24 +11,47 @@ npm install @zudojs/docs
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
createDocumentRegistry,
|
|
16
|
+
createMarkdownDocument,
|
|
17
|
+
validateAll,
|
|
18
|
+
generateIndex,
|
|
19
|
+
} from "@zudojs/docs";
|
|
15
20
|
|
|
16
21
|
const registry = createDocumentRegistry();
|
|
17
22
|
|
|
23
|
+
registry.register(
|
|
24
|
+
createMarkdownDocument(
|
|
25
|
+
"getting-started",
|
|
26
|
+
"Getting Started",
|
|
27
|
+
"# Getting Started\n\nWelcome to Zudojs...",
|
|
28
|
+
{ category: "introduction", tags: ["intro"] },
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Or build the document object yourself
|
|
18
33
|
registry.register({
|
|
19
|
-
id: "
|
|
20
|
-
title: "
|
|
21
|
-
content: "#
|
|
34
|
+
id: "guides.http.routing",
|
|
35
|
+
title: "HTTP Routing",
|
|
36
|
+
content: { type: "markdown", value: "# Routing\n\n..." },
|
|
22
37
|
});
|
|
38
|
+
|
|
39
|
+
const result = validateAll(registry.getAll());
|
|
40
|
+
if (!result.valid) {
|
|
41
|
+
console.error(result.issues);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const index = generateIndex(registry.getAll()); // SERVER-only docs excluded
|
|
23
45
|
```
|
|
24
46
|
|
|
25
47
|
## Features
|
|
26
48
|
|
|
27
|
-
- Document model with frontmatter
|
|
28
|
-
- Document registry and discovery
|
|
29
|
-
- Navigation tree
|
|
30
|
-
- Markdown and JSON generation
|
|
31
|
-
- Document validation
|
|
49
|
+
- Document model with frontmatter (`parseFrontmatter` / `serializeFrontmatter` round-trip safely)
|
|
50
|
+
- Document registry and discovery (`DuplicateDocumentError` on duplicate IDs, deep-frozen copies)
|
|
51
|
+
- Navigation tree helpers (breadcrumbs, siblings, previous/next, cycle-safe walkers)
|
|
52
|
+
- Markdown and JSON generation with escaping for untrusted content and `visibility` filtering
|
|
53
|
+
- Document, link and navigation validation with a single `valid` rule (errors only)
|
|
54
|
+
- Documentation error classes re-exported from `@zudojs/errors`
|
|
32
55
|
|
|
33
56
|
## Use Cases
|
|
34
57
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Core type definitions for the documentation model.
|
|
5
5
|
*/
|
|
6
6
|
export type { DocumentationNode, HeadingNode, ParagraphNode, CodeNode, ListNode, LinkNode, TableNode, QuoteNode, CalloutNode, MarkdownContent, MDXContent, HTMLContent, StructuredContent, DocumentationContent, } from "./documentationContent.js";
|
|
7
|
-
export type { DocumentationCategory, DocumentationStatus, APISymbolKind, SourceLocation, APISymbol, APIParameter, DocumentationMetadata, DocumentationVersion, } from "./documentationMetadata.js";
|
|
7
|
+
export type { DocumentationCategory, DocumentationStatus, APISymbolKind, SourceLocation, APISymbol, APIExample, APIParameter, DocumentationMetadata, DocumentationVersion, } from "./documentationMetadata.js";
|
|
8
8
|
export type { DocumentationDocument } from "./documentationDocument.js";
|
|
9
9
|
export type { DocumentationNavigationItem, DocumentationBreadcrumb, SearchDocument, SearchResult, } from "./documentationNavigation.js";
|
|
10
10
|
export type { DocumentationProvider, DocumentationSourceLoader, DocumentationSanitizer, } from "./documentationProvider.js";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides a chainable API that validates inputs and
|
|
5
5
|
* produces immutable DocumentationDocument objects.
|
|
6
6
|
*/
|
|
7
|
-
import type { DocumentationContent, DocumentationDocument, DocumentationCategory, DocumentationMetadata, DocumentationStatus } from "../docsTypes/index.js";
|
|
7
|
+
import type { DocumentationContent, DocumentationDocument, DocumentationCategory, DocumentationMetadata, DocumentationNode, DocumentationStatus } from "../docsTypes/index.js";
|
|
8
8
|
/**
|
|
9
9
|
* Options for creating a document via the builder.
|
|
10
10
|
*/
|
|
@@ -22,9 +22,21 @@ export interface DocumentBuilderOptions {
|
|
|
22
22
|
readonly deprecatedMessage?: string;
|
|
23
23
|
readonly visibility?: "SERVER" | "CLIENT";
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Options accepted by the convenience builders. The positional
|
|
27
|
+
* arguments (`id`, `title`, content) always win over these.
|
|
28
|
+
*/
|
|
29
|
+
export type DocumentBuilderExtras = Omit<Partial<DocumentBuilderOptions>, "id" | "title" | "content">;
|
|
25
30
|
/**
|
|
26
31
|
* Creates a documentation document from structured options.
|
|
27
32
|
*
|
|
33
|
+
* The returned document is a deep-frozen copy: later mutation of the
|
|
34
|
+
* options object (or of nested `content`, `metadata`, `tags`) does not
|
|
35
|
+
* affect it.
|
|
36
|
+
*
|
|
37
|
+
* @throws {DocumentValidationError} when `id`, `title` or `content` is missing
|
|
38
|
+
* or the ID is not a valid dot-separated identifier.
|
|
39
|
+
*
|
|
28
40
|
* @example
|
|
29
41
|
* ```ts
|
|
30
42
|
* const doc = createDocument({
|
|
@@ -40,11 +52,9 @@ export declare function createDocument(options: DocumentBuilderOptions): Documen
|
|
|
40
52
|
/**
|
|
41
53
|
* Creates a markdown document.
|
|
42
54
|
*/
|
|
43
|
-
export declare function createMarkdownDocument(id: string, title: string, markdown: string, options?:
|
|
55
|
+
export declare function createMarkdownDocument(id: string, title: string, markdown: string, options?: DocumentBuilderExtras): DocumentationDocument;
|
|
44
56
|
/**
|
|
45
57
|
* Creates a structured document from AST nodes.
|
|
46
58
|
*/
|
|
47
|
-
export declare function createStructuredDocument(id: string, title: string, nodes:
|
|
48
|
-
readonly nodes: infer N;
|
|
49
|
-
} ? N : never, options?: Partial<DocumentBuilderOptions>): DocumentationDocument;
|
|
59
|
+
export declare function createStructuredDocument(id: string, title: string, nodes: readonly DocumentationNode[], options?: DocumentBuilderExtras): DocumentationDocument;
|
|
50
60
|
//# sourceMappingURL=documentBuilder.core.d.ts.map
|
|
@@ -4,9 +4,19 @@
|
|
|
4
4
|
* Provides a chainable API that validates inputs and
|
|
5
5
|
* produces immutable DocumentationDocument objects.
|
|
6
6
|
*/
|
|
7
|
+
import { DocumentValidationError } from "@zudojs/errors";
|
|
8
|
+
import { deepFreezeClone } from "../utils/utils.freeze.js";
|
|
9
|
+
import { isValidDocumentId } from "../utils/utils.helper.js";
|
|
7
10
|
/**
|
|
8
11
|
* Creates a documentation document from structured options.
|
|
9
12
|
*
|
|
13
|
+
* The returned document is a deep-frozen copy: later mutation of the
|
|
14
|
+
* options object (or of nested `content`, `metadata`, `tags`) does not
|
|
15
|
+
* affect it.
|
|
16
|
+
*
|
|
17
|
+
* @throws {DocumentValidationError} when `id`, `title` or `content` is missing
|
|
18
|
+
* or the ID is not a valid dot-separated identifier.
|
|
19
|
+
*
|
|
10
20
|
* @example
|
|
11
21
|
* ```ts
|
|
12
22
|
* const doc = createDocument({
|
|
@@ -20,34 +30,39 @@
|
|
|
20
30
|
*/
|
|
21
31
|
export function createDocument(options) {
|
|
22
32
|
validateDocumentOptions(options);
|
|
23
|
-
|
|
33
|
+
const document = {
|
|
24
34
|
id: options.id,
|
|
25
35
|
title: options.title,
|
|
26
36
|
description: options.description,
|
|
27
37
|
content: options.content,
|
|
28
38
|
category: options.category,
|
|
29
|
-
tags: options.tags ?
|
|
39
|
+
tags: options.tags ? [...options.tags] : undefined,
|
|
30
40
|
version: options.version,
|
|
31
41
|
status: options.status,
|
|
32
42
|
metadata: options.metadata,
|
|
33
43
|
deprecated: options.deprecated,
|
|
34
44
|
deprecatedMessage: options.deprecatedMessage,
|
|
35
45
|
visibility: options.visibility,
|
|
36
|
-
}
|
|
46
|
+
};
|
|
47
|
+
return deepFreezeClone(document);
|
|
37
48
|
}
|
|
38
49
|
/**
|
|
39
50
|
* Validates document builder options.
|
|
40
51
|
* Throws on invalid input.
|
|
41
52
|
*/
|
|
42
53
|
function validateDocumentOptions(options) {
|
|
43
|
-
if (
|
|
44
|
-
throw new
|
|
54
|
+
if (typeof options.id !== "string" || options.id.trim().length === 0) {
|
|
55
|
+
throw new DocumentValidationError("Document ID is required.");
|
|
56
|
+
}
|
|
57
|
+
if (!isValidDocumentId(options.id)) {
|
|
58
|
+
throw new DocumentValidationError(`Document ID "${options.id}" is invalid. Use dot-separated segments of letters, digits, "_" and "-".`, options.id);
|
|
45
59
|
}
|
|
46
|
-
if (
|
|
47
|
-
|
|
60
|
+
if (typeof options.title !== "string" ||
|
|
61
|
+
options.title.trim().length === 0) {
|
|
62
|
+
throw new DocumentValidationError("Document title is required.", options.id);
|
|
48
63
|
}
|
|
49
|
-
if (!options.content) {
|
|
50
|
-
throw new
|
|
64
|
+
if (!options.content || typeof options.content !== "object") {
|
|
65
|
+
throw new DocumentValidationError("Document content is required.", options.id);
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
68
|
/**
|
|
@@ -55,10 +70,10 @@ function validateDocumentOptions(options) {
|
|
|
55
70
|
*/
|
|
56
71
|
export function createMarkdownDocument(id, title, markdown, options) {
|
|
57
72
|
return createDocument({
|
|
73
|
+
...options,
|
|
58
74
|
id,
|
|
59
75
|
title,
|
|
60
76
|
content: { type: "markdown", value: markdown },
|
|
61
|
-
...options,
|
|
62
77
|
});
|
|
63
78
|
}
|
|
64
79
|
/**
|
|
@@ -66,13 +81,10 @@ export function createMarkdownDocument(id, title, markdown, options) {
|
|
|
66
81
|
*/
|
|
67
82
|
export function createStructuredDocument(id, title, nodes, options) {
|
|
68
83
|
return createDocument({
|
|
84
|
+
...options,
|
|
69
85
|
id,
|
|
70
86
|
title,
|
|
71
|
-
content: {
|
|
72
|
-
type: "structured",
|
|
73
|
-
value: nodes,
|
|
74
|
-
},
|
|
75
|
-
...options,
|
|
87
|
+
content: { type: "structured", nodes },
|
|
76
88
|
});
|
|
77
89
|
}
|
|
78
90
|
//# sourceMappingURL=documentBuilder.core.js.map
|
package/dist/document/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Document model and builder for creating documentation pages.
|
|
5
5
|
*/
|
|
6
6
|
export { createDocument, createMarkdownDocument, createStructuredDocument, } from "./documentBuilder.core.js";
|
|
7
|
-
export type { DocumentBuilderOptions } from "./documentBuilder.core.js";
|
|
7
|
+
export type { DocumentBuilderOptions, DocumentBuilderExtras, } from "./documentBuilder.core.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/docs/errors
|
|
3
|
+
*
|
|
4
|
+
* The documentation error hierarchy is defined in `@zudojs/errors` and
|
|
5
|
+
* re-exported here so consumers can discriminate registry and builder
|
|
6
|
+
* failures with `instanceof` without a second import.
|
|
7
|
+
*/
|
|
8
|
+
import { DocumentationError } from "@zudojs/errors";
|
|
9
|
+
import type { DocumentationErrorOptions } from "@zudojs/errors";
|
|
10
|
+
export { DocumentationError, DocumentParseError, DocumentValidationError, DuplicateDocumentError, DocumentNotFoundError, BrokenDocumentationLinkError, InvalidFrontmatterError, InvalidNavigationError, ExampleValidationError, GenerationError, DocumentationVersionError, } from "@zudojs/errors";
|
|
11
|
+
export type { DocumentationErrorOptions } from "@zudojs/errors";
|
|
12
|
+
/** Creates a generic documentation error. */
|
|
13
|
+
export declare function createDocumentationError(message: string, options?: DocumentationErrorOptions): DocumentationError;
|
|
14
|
+
/** Determines whether an unknown value is a `DocumentationError`. */
|
|
15
|
+
export declare function isDocumentationError(value: unknown): value is DocumentationError;
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/docs/errors
|
|
3
|
+
*
|
|
4
|
+
* The documentation error hierarchy is defined in `@zudojs/errors` and
|
|
5
|
+
* re-exported here so consumers can discriminate registry and builder
|
|
6
|
+
* failures with `instanceof` without a second import.
|
|
7
|
+
*/
|
|
8
|
+
import { DocumentationError } from "@zudojs/errors";
|
|
9
|
+
export { DocumentationError, DocumentParseError, DocumentValidationError, DuplicateDocumentError, DocumentNotFoundError, BrokenDocumentationLinkError, InvalidFrontmatterError, InvalidNavigationError, ExampleValidationError, GenerationError, DocumentationVersionError, } from "@zudojs/errors";
|
|
10
|
+
/** Creates a generic documentation error. */
|
|
11
|
+
export function createDocumentationError(message, options) {
|
|
12
|
+
return new DocumentationError(message, options);
|
|
13
|
+
}
|
|
14
|
+
/** Determines whether an unknown value is a `DocumentationError`. */
|
|
15
|
+
export function isDocumentationError(value) {
|
|
16
|
+
return value instanceof DocumentationError;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -29,6 +29,10 @@ export interface ExampleValidationResult {
|
|
|
29
29
|
export declare function validateExample(example: DocumentationExample): ExampleValidationResult;
|
|
30
30
|
/**
|
|
31
31
|
* Renders an example as a markdown code block.
|
|
32
|
+
*
|
|
33
|
+
* The fence is chosen longer than any backtick run inside the code and
|
|
34
|
+
* the language is reduced to fence-safe characters, so example content
|
|
35
|
+
* cannot escape its code block.
|
|
32
36
|
*/
|
|
33
37
|
export declare function renderExampleMarkdown(example: DocumentationExample): string;
|
|
34
38
|
/**
|
|
@@ -4,19 +4,22 @@
|
|
|
4
4
|
* Examples are first-class objects that can be validated,
|
|
5
5
|
* rendered, and included in generated documentation.
|
|
6
6
|
*/
|
|
7
|
+
import { fenceFor, sanitizeLanguage } from "../generator/generatorMarkdownNodes.js";
|
|
7
8
|
/**
|
|
8
9
|
* Validates a documentation example.
|
|
9
10
|
*/
|
|
10
11
|
export function validateExample(example) {
|
|
11
12
|
const errors = [];
|
|
12
|
-
|
|
13
|
+
const hasId = typeof example.id === "string" && example.id.trim().length > 0;
|
|
14
|
+
const label = hasId ? `Example "${example.id}"` : "Example";
|
|
15
|
+
if (!hasId) {
|
|
13
16
|
errors.push("Example ID is required.");
|
|
14
17
|
}
|
|
15
18
|
if (!example.language || example.language.trim().length === 0) {
|
|
16
|
-
errors.push(
|
|
19
|
+
errors.push(`${label} requires a language.`);
|
|
17
20
|
}
|
|
18
21
|
if (!example.code || example.code.trim().length === 0) {
|
|
19
|
-
errors.push(
|
|
22
|
+
errors.push(`${label} requires code content.`);
|
|
20
23
|
}
|
|
21
24
|
return {
|
|
22
25
|
valid: errors.length === 0,
|
|
@@ -25,20 +28,26 @@ export function validateExample(example) {
|
|
|
25
28
|
}
|
|
26
29
|
/**
|
|
27
30
|
* Renders an example as a markdown code block.
|
|
31
|
+
*
|
|
32
|
+
* The fence is chosen longer than any backtick run inside the code and
|
|
33
|
+
* the language is reduced to fence-safe characters, so example content
|
|
34
|
+
* cannot escape its code block.
|
|
28
35
|
*/
|
|
29
36
|
export function renderExampleMarkdown(example) {
|
|
30
37
|
const lines = [];
|
|
38
|
+
const code = (example.code ?? "").trim();
|
|
39
|
+
const fence = fenceFor(code);
|
|
31
40
|
if (example.title) {
|
|
32
|
-
lines.push(`### ${example.title}`);
|
|
41
|
+
lines.push(`### ${example.title.replace(/\r?\n/g, " ")}`);
|
|
33
42
|
lines.push("");
|
|
34
43
|
}
|
|
35
44
|
if (example.description) {
|
|
36
45
|
lines.push(example.description);
|
|
37
46
|
lines.push("");
|
|
38
47
|
}
|
|
39
|
-
lines.push(
|
|
40
|
-
lines.push(
|
|
41
|
-
lines.push(
|
|
48
|
+
lines.push(fence + sanitizeLanguage(example.language));
|
|
49
|
+
lines.push(code);
|
|
50
|
+
lines.push(fence);
|
|
42
51
|
return lines.join("\n");
|
|
43
52
|
}
|
|
44
53
|
/**
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { parseFrontmatter } from "./frontmatter.parser.js";
|
|
5
5
|
export { serializeFrontmatter } from "./frontmatter.serializer.js";
|
|
6
|
-
export type { ParsedFrontmatter, FrontmatterMetadata, } from "./frontmatter.types.js";
|
|
6
|
+
export type { ParsedFrontmatter, FrontmatterMetadata, FrontmatterScalar, FrontmatterValue, } from "./frontmatter.types.js";
|
|
7
7
|
//# sourceMappingURL=frontmatter.core.d.ts.map
|
|
@@ -3,13 +3,26 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Parses YAML-like frontmatter delimited by `---` markers
|
|
5
5
|
* and extracts metadata alongside the remaining content.
|
|
6
|
+
*
|
|
7
|
+
* Supported subset:
|
|
8
|
+
* - `key: scalar` (strings, canonical numbers, booleans, null)
|
|
9
|
+
* - single- and double-quoted strings with escapes
|
|
10
|
+
* - `key:` followed by `- item` lines (list of scalars)
|
|
11
|
+
* - one level of nested mapping (`key:` followed by indented `sub: value`)
|
|
12
|
+
* - `#` comments (outside quotes)
|
|
13
|
+
*
|
|
14
|
+
* Anything else is kept as a plain string. Keys that would alter the
|
|
15
|
+
* result object's prototype (`__proto__`, `constructor`, `prototype`)
|
|
16
|
+
* are rejected.
|
|
6
17
|
*/
|
|
7
18
|
import type { ParsedFrontmatter } from "./frontmatter.types.js";
|
|
8
19
|
/**
|
|
9
20
|
* Parses YAML-like frontmatter from a markdown string.
|
|
10
21
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
22
|
+
* The opening delimiter must be exactly `---` on the first non-blank
|
|
23
|
+
* line and the closing delimiter must be `---` on its own line.
|
|
24
|
+
* Documents without a well-formed block are returned unchanged with
|
|
25
|
+
* empty metadata.
|
|
13
26
|
*/
|
|
14
27
|
export declare function parseFrontmatter(raw: string): ParsedFrontmatter;
|
|
15
28
|
//# sourceMappingURL=frontmatter.parser.d.ts.map
|
|
@@ -3,80 +3,243 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Parses YAML-like frontmatter delimited by `---` markers
|
|
5
5
|
* and extracts metadata alongside the remaining content.
|
|
6
|
+
*
|
|
7
|
+
* Supported subset:
|
|
8
|
+
* - `key: scalar` (strings, canonical numbers, booleans, null)
|
|
9
|
+
* - single- and double-quoted strings with escapes
|
|
10
|
+
* - `key:` followed by `- item` lines (list of scalars)
|
|
11
|
+
* - one level of nested mapping (`key:` followed by indented `sub: value`)
|
|
12
|
+
* - `#` comments (outside quotes)
|
|
13
|
+
*
|
|
14
|
+
* Anything else is kept as a plain string. Keys that would alter the
|
|
15
|
+
* result object's prototype (`__proto__`, `constructor`, `prototype`)
|
|
16
|
+
* are rejected.
|
|
6
17
|
*/
|
|
7
18
|
const FRONTMATTER_DELIMITER = "---";
|
|
19
|
+
/** Keys that must never be assigned onto a plain object. */
|
|
20
|
+
const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
21
|
+
/** Valid frontmatter key syntax. */
|
|
22
|
+
const KEY_PATTERN = /^[A-Za-z_][\w.-]*$/;
|
|
23
|
+
/** Canonical number: optional sign, no leading zeros, optional fraction. */
|
|
24
|
+
const CANONICAL_NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?$/;
|
|
25
|
+
/**
|
|
26
|
+
* Keys whose values are declared as strings in `FrontmatterMetadata`.
|
|
27
|
+
* Their scalars are never coerced, so `version: 1.0` stays `"1.0"`.
|
|
28
|
+
*/
|
|
29
|
+
const STRING_KEYS = new Set([
|
|
30
|
+
"title",
|
|
31
|
+
"description",
|
|
32
|
+
"category",
|
|
33
|
+
"version",
|
|
34
|
+
"status",
|
|
35
|
+
"deprecatedMessage",
|
|
36
|
+
"visibility",
|
|
37
|
+
]);
|
|
38
|
+
/** Keys whose list items are always strings. */
|
|
39
|
+
const STRING_LIST_KEYS = new Set(["tags"]);
|
|
8
40
|
/**
|
|
9
41
|
* Parses YAML-like frontmatter from a markdown string.
|
|
10
42
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
43
|
+
* The opening delimiter must be exactly `---` on the first non-blank
|
|
44
|
+
* line and the closing delimiter must be `---` on its own line.
|
|
45
|
+
* Documents without a well-formed block are returned unchanged with
|
|
46
|
+
* empty metadata.
|
|
13
47
|
*/
|
|
14
48
|
export function parseFrontmatter(raw) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
49
|
+
const lines = raw.split(/\r?\n/);
|
|
50
|
+
let start = 0;
|
|
51
|
+
while (start < lines.length && lines[start]?.trim() === "") {
|
|
52
|
+
start++;
|
|
18
53
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (endIndex === -1) {
|
|
22
|
-
return { metadata: {}, content: raw };
|
|
54
|
+
if (lines[start]?.trim() !== FRONTMATTER_DELIMITER) {
|
|
55
|
+
return { metadata: Object.freeze({}), content: raw };
|
|
23
56
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
57
|
+
let end = -1;
|
|
58
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
59
|
+
if (lines[i]?.trim() === FRONTMATTER_DELIMITER) {
|
|
60
|
+
end = i;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (end === -1) {
|
|
65
|
+
return { metadata: Object.freeze({}), content: raw };
|
|
66
|
+
}
|
|
67
|
+
const yamlLines = lines.slice(start + 1, end);
|
|
68
|
+
const remainingContent = lines
|
|
69
|
+
.slice(end + 1)
|
|
70
|
+
.join("\n")
|
|
71
|
+
.replace(/^\s*\n/, "")
|
|
27
72
|
.trimStart();
|
|
28
|
-
const metadata = parseYamlLike(
|
|
73
|
+
const metadata = parseYamlLike(yamlLines);
|
|
29
74
|
return { metadata, content: remainingContent };
|
|
30
75
|
}
|
|
31
76
|
/**
|
|
32
77
|
* Minimal YAML-like parser for frontmatter key-value pairs.
|
|
33
78
|
*/
|
|
34
|
-
function parseYamlLike(
|
|
35
|
-
const result =
|
|
36
|
-
const lines = yaml.split("\n");
|
|
79
|
+
function parseYamlLike(lines) {
|
|
80
|
+
const result = Object.create(null);
|
|
37
81
|
let currentKey = null;
|
|
38
82
|
let currentArray = null;
|
|
39
|
-
|
|
83
|
+
let currentMap = null;
|
|
84
|
+
const flush = () => {
|
|
85
|
+
if (currentKey === null)
|
|
86
|
+
return;
|
|
87
|
+
if (currentArray) {
|
|
88
|
+
result[currentKey] = Object.freeze(currentArray);
|
|
89
|
+
}
|
|
90
|
+
else if (currentMap) {
|
|
91
|
+
result[currentKey] = Object.freeze(currentMap);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
result[currentKey] = "";
|
|
95
|
+
}
|
|
96
|
+
currentKey = null;
|
|
97
|
+
currentArray = null;
|
|
98
|
+
currentMap = null;
|
|
99
|
+
};
|
|
100
|
+
for (const rawLine of lines) {
|
|
101
|
+
const line = stripComment(rawLine);
|
|
40
102
|
const trimmed = line.trim();
|
|
41
|
-
if (trimmed
|
|
42
|
-
if (currentKey && currentArray) {
|
|
43
|
-
currentArray.push(trimmed.slice(2).trim());
|
|
44
|
-
}
|
|
103
|
+
if (trimmed === "")
|
|
45
104
|
continue;
|
|
105
|
+
const indented = /^\s/.test(line);
|
|
106
|
+
if (currentKey !== null) {
|
|
107
|
+
if (trimmed.startsWith("- ") || trimmed === "-") {
|
|
108
|
+
if (currentMap) {
|
|
109
|
+
flush();
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
currentArray ??= [];
|
|
113
|
+
const item = trimmed.slice(1).trim();
|
|
114
|
+
currentArray.push(STRING_LIST_KEYS.has(currentKey)
|
|
115
|
+
? parseStringScalar(item)
|
|
116
|
+
: parseScalar(item));
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (indented && !currentArray) {
|
|
121
|
+
const entry = splitKeyValue(trimmed);
|
|
122
|
+
if (entry) {
|
|
123
|
+
currentMap ??= Object.create(null);
|
|
124
|
+
if (isAllowedKey(entry.key)) {
|
|
125
|
+
currentMap[entry.key] = parseScalar(entry.value);
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
flush();
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
flush();
|
|
133
|
+
}
|
|
46
134
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
currentArray = null;
|
|
50
|
-
currentKey = null;
|
|
51
|
-
}
|
|
52
|
-
const colonIndex = trimmed.indexOf(":");
|
|
53
|
-
if (colonIndex === -1)
|
|
135
|
+
const entry = splitKeyValue(trimmed);
|
|
136
|
+
if (!entry)
|
|
54
137
|
continue;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (value === "") {
|
|
58
|
-
currentKey = key;
|
|
59
|
-
currentArray = [];
|
|
138
|
+
if (!isAllowedKey(entry.key))
|
|
139
|
+
continue;
|
|
140
|
+
if (entry.value === "") {
|
|
141
|
+
currentKey = entry.key;
|
|
60
142
|
continue;
|
|
61
143
|
}
|
|
62
|
-
result[key] =
|
|
144
|
+
result[entry.key] = STRING_KEYS.has(entry.key)
|
|
145
|
+
? parseStringScalar(entry.value)
|
|
146
|
+
: parseScalar(entry.value);
|
|
63
147
|
}
|
|
64
|
-
|
|
65
|
-
|
|
148
|
+
flush();
|
|
149
|
+
return Object.freeze({ ...result });
|
|
150
|
+
}
|
|
151
|
+
/** Splits `key: value` into its parts; returns undefined when not a pair. */
|
|
152
|
+
function splitKeyValue(line) {
|
|
153
|
+
const match = line.match(/^([^:]+?)\s*:(?:\s+(.*)|\s*)$/);
|
|
154
|
+
if (!match)
|
|
155
|
+
return undefined;
|
|
156
|
+
const key = match[1]?.trim() ?? "";
|
|
157
|
+
const value = match[2]?.trim() ?? "";
|
|
158
|
+
if (key === "")
|
|
159
|
+
return undefined;
|
|
160
|
+
return { key, value };
|
|
161
|
+
}
|
|
162
|
+
/** Rejects prototype-polluting and syntactically invalid keys. */
|
|
163
|
+
function isAllowedKey(key) {
|
|
164
|
+
return !FORBIDDEN_KEYS.has(key) && KEY_PATTERN.test(key);
|
|
165
|
+
}
|
|
166
|
+
/** Removes a trailing `# comment` that is not inside quotes. */
|
|
167
|
+
function stripComment(line) {
|
|
168
|
+
let inSingle = false;
|
|
169
|
+
let inDouble = false;
|
|
170
|
+
for (let i = 0; i < line.length; i++) {
|
|
171
|
+
const ch = line[i];
|
|
172
|
+
if (ch === "\\" && inDouble) {
|
|
173
|
+
i++;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (ch === '"' && !inSingle)
|
|
177
|
+
inDouble = !inDouble;
|
|
178
|
+
else if (ch === "'" && !inDouble)
|
|
179
|
+
inSingle = !inSingle;
|
|
180
|
+
else if (ch === "#" &&
|
|
181
|
+
!inSingle &&
|
|
182
|
+
!inDouble &&
|
|
183
|
+
(i === 0 || /\s/.test(line[i - 1] ?? ""))) {
|
|
184
|
+
return line.slice(0, i);
|
|
185
|
+
}
|
|
66
186
|
}
|
|
67
|
-
return
|
|
187
|
+
return line;
|
|
68
188
|
}
|
|
69
189
|
/**
|
|
70
190
|
* Parses a scalar YAML value into its appropriate JS type.
|
|
191
|
+
*
|
|
192
|
+
* Quoted strings are unquoted (with escape handling) and never coerced.
|
|
193
|
+
* Only canonical numbers within the safe-integer range become numbers;
|
|
194
|
+
* anything else stays a string.
|
|
71
195
|
*/
|
|
72
196
|
function parseScalar(value) {
|
|
197
|
+
if (value.length >= 2) {
|
|
198
|
+
const first = value[0];
|
|
199
|
+
const last = value[value.length - 1];
|
|
200
|
+
if (first === '"' && last === '"') {
|
|
201
|
+
return unescapeDoubleQuoted(value.slice(1, -1));
|
|
202
|
+
}
|
|
203
|
+
if (first === "'" && last === "'") {
|
|
204
|
+
return value.slice(1, -1).replace(/''/g, "'");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
73
207
|
if (value === "true")
|
|
74
208
|
return true;
|
|
75
209
|
if (value === "false")
|
|
76
210
|
return false;
|
|
77
|
-
if (
|
|
78
|
-
return
|
|
211
|
+
if (value === "null" || value === "~")
|
|
212
|
+
return null;
|
|
213
|
+
if (CANONICAL_NUMBER.test(value)) {
|
|
214
|
+
const parsed = Number(value);
|
|
215
|
+
if (Number.isFinite(parsed) && Math.abs(parsed) <= Number.MAX_SAFE_INTEGER) {
|
|
216
|
+
return parsed;
|
|
217
|
+
}
|
|
79
218
|
}
|
|
80
219
|
return value;
|
|
81
220
|
}
|
|
221
|
+
/** Parses a scalar that must remain a string (unquotes, never coerces). */
|
|
222
|
+
function parseStringScalar(value) {
|
|
223
|
+
const parsed = parseScalar(value);
|
|
224
|
+
if (typeof parsed === "string")
|
|
225
|
+
return parsed;
|
|
226
|
+
if (parsed === null)
|
|
227
|
+
return "";
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
/** Handles `\"`, `\\`, `\n`, `\t`, `\r` escapes in double-quoted strings. */
|
|
231
|
+
function unescapeDoubleQuoted(value) {
|
|
232
|
+
return value.replace(/\\(["\\nrt])/g, (_, ch) => {
|
|
233
|
+
switch (ch) {
|
|
234
|
+
case "n":
|
|
235
|
+
return "\n";
|
|
236
|
+
case "r":
|
|
237
|
+
return "\r";
|
|
238
|
+
case "t":
|
|
239
|
+
return "\t";
|
|
240
|
+
default:
|
|
241
|
+
return ch;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
82
245
|
//# sourceMappingURL=frontmatter.parser.js.map
|