@waveso/docs 0.2.0 → 0.3.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/CHANGELOG.md +110 -0
- package/README.md +490 -75
- package/dist/code-frame.d.ts +29 -0
- package/dist/code-frame.js +41 -0
- package/dist/code-meta.d.ts +48 -0
- package/dist/code-meta.js +72 -0
- package/dist/docs-content-id.d.ts +19 -0
- package/dist/docs-content-id.js +19 -0
- package/dist/docs-error.d.ts +2 -57
- package/dist/docs-error.js +3 -15
- package/dist/errors.d.ts +94 -0
- package/dist/errors.js +45 -0
- package/dist/next.d.ts +153 -28
- package/dist/next.js +65 -33
- package/dist/plugins/rehype-capture-toc.js +26 -5
- package/dist/plugins/rehype-code-frame.d.ts +10 -0
- package/dist/plugins/rehype-code-frame.js +88 -0
- package/dist/plugins/rehype-code-language.js +7 -1
- package/dist/react/code-runtime.d.ts +14 -0
- package/dist/react/code-runtime.js +161 -0
- package/dist/react/doc-content.d.ts +39 -2
- package/dist/react/doc-content.js +42 -10
- package/dist/react/layout.d.ts +44 -0
- package/dist/react/layout.js +65 -0
- package/dist/react/nav.d.ts +28 -0
- package/dist/react/nav.js +70 -0
- package/dist/react/nearest-scroll-top.d.ts +45 -0
- package/dist/react/nearest-scroll-top.js +44 -0
- package/dist/react/next-link.d.ts +34 -0
- package/dist/react/next-link.js +30 -0
- package/dist/react/next-nav.d.ts +11 -0
- package/dist/react/next-nav.js +32 -0
- package/dist/react/next-search.d.ts +22 -0
- package/dist/react/next-search.js +52 -0
- package/dist/react/search-dialog.d.ts +20 -8
- package/dist/react/search-dialog.js +15 -10
- package/dist/react/shell-labels.d.ts +43 -0
- package/dist/react/shell-labels.js +27 -0
- package/dist/react/sidebar.d.ts +38 -3
- package/dist/react/sidebar.js +104 -12
- package/dist/react/skip-link.d.ts +1 -9
- package/dist/react/skip-link.js +6 -5
- package/dist/react/toc.d.ts +12 -4
- package/dist/react/toc.js +18 -7
- package/dist/react/youtube.d.ts +31 -5
- package/dist/react/youtube.js +76 -54
- package/dist/render.d.ts +35 -1
- package/dist/render.js +35 -14
- package/dist/route-path.d.ts +46 -0
- package/dist/route-path.js +51 -0
- package/dist/search-index.d.ts +6 -23
- package/dist/search-index.js +6 -51
- package/dist/sitemap-limit.d.ts +34 -0
- package/dist/sitemap-limit.js +37 -0
- package/dist/source.d.ts +1 -23
- package/dist/source.js +40 -43
- package/dist/styles.css +939 -93
- package/dist/types.d.ts +11 -2
- package/package.json +58 -23
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Root } from "hast";
|
|
2
|
+
//#region src/code-frame.d.ts
|
|
3
|
+
/** Marks the `<figure>` a code block is wrapped in. */
|
|
4
|
+
declare const CODE_FRAME_ATTRIBUTE = "data-wave-docs-code";
|
|
5
|
+
/** The copy button inside that figure. */
|
|
6
|
+
declare const CODE_COPY_ATTRIBUTE = "data-wave-docs-copy";
|
|
7
|
+
/**
|
|
8
|
+
* Set on `<html>` by the runtime once its listener is attached.
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ THIS IS WHAT CLOSES THE INERT-MARKUP TRAP, AND IT IS STRUCTURAL RATHER
|
|
11
|
+
* THAN DOCUMENTED. The button is in the HTML whether or not any JavaScript
|
|
12
|
+
* runs — a `renderToStaticMarkup` consumer, a reader with scripts off, anyone
|
|
13
|
+
* rendering the hast by hand. The stylesheet keeps it `visibility: hidden`
|
|
14
|
+
* until this attribute appears, which also takes it out of the tab order, so
|
|
15
|
+
* none of those readers meets a button that silently does nothing or a dead
|
|
16
|
+
* tab stop where a control should be.
|
|
17
|
+
*/
|
|
18
|
+
declare const CODE_READY_ATTRIBUTE = "data-wave-docs-code-ready";
|
|
19
|
+
/**
|
|
20
|
+
* Whether a tree contains a code frame.
|
|
21
|
+
*
|
|
22
|
+
* `DocContent` asks before rendering the runtime, so a page with no fences
|
|
23
|
+
* ships zero extra bytes rather than a client component that finds nothing to
|
|
24
|
+
* do. The server has the tree in hand and this is one pass over it, so the
|
|
25
|
+
* check costs nothing the render was not already paying.
|
|
26
|
+
*/
|
|
27
|
+
declare function hasCodeFrame(tree: Root): boolean;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { CODE_COPY_ATTRIBUTE, CODE_FRAME_ATTRIBUTE, CODE_READY_ATTRIBUTE, hasCodeFrame };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/code-frame.ts
|
|
2
|
+
/** Marks the `<figure>` a code block is wrapped in. */
|
|
3
|
+
const CODE_FRAME_ATTRIBUTE = "data-wave-docs-code";
|
|
4
|
+
/** The copy button inside that figure. */
|
|
5
|
+
const CODE_COPY_ATTRIBUTE = "data-wave-docs-copy";
|
|
6
|
+
/**
|
|
7
|
+
* Set on `<html>` by the runtime once its listener is attached.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ THIS IS WHAT CLOSES THE INERT-MARKUP TRAP, AND IT IS STRUCTURAL RATHER
|
|
10
|
+
* THAN DOCUMENTED. The button is in the HTML whether or not any JavaScript
|
|
11
|
+
* runs — a `renderToStaticMarkup` consumer, a reader with scripts off, anyone
|
|
12
|
+
* rendering the hast by hand. The stylesheet keeps it `visibility: hidden`
|
|
13
|
+
* until this attribute appears, which also takes it out of the tab order, so
|
|
14
|
+
* none of those readers meets a button that silently does nothing or a dead
|
|
15
|
+
* tab stop where a control should be.
|
|
16
|
+
*/
|
|
17
|
+
const CODE_READY_ATTRIBUTE = "data-wave-docs-code-ready";
|
|
18
|
+
/**
|
|
19
|
+
* Whether a tree contains a code frame.
|
|
20
|
+
*
|
|
21
|
+
* `DocContent` asks before rendering the runtime, so a page with no fences
|
|
22
|
+
* ships zero extra bytes rather than a client component that finds nothing to
|
|
23
|
+
* do. The server has the tree in hand and this is one pass over it, so the
|
|
24
|
+
* check costs nothing the render was not already paying.
|
|
25
|
+
*/
|
|
26
|
+
function hasCodeFrame(tree) {
|
|
27
|
+
return containsFrame(tree.children);
|
|
28
|
+
}
|
|
29
|
+
function containsFrame(nodes) {
|
|
30
|
+
for (const node of nodes) {
|
|
31
|
+
if (node.type !== "element") continue;
|
|
32
|
+
if (isCodeFrame(node)) return true;
|
|
33
|
+
if (containsFrame(node.children)) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
function isCodeFrame(node) {
|
|
38
|
+
return node.properties[CODE_FRAME_ATTRIBUTE] !== void 0;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { CODE_COPY_ATTRIBUTE, CODE_FRAME_ATTRIBUTE, CODE_READY_ATTRIBUTE, hasCodeFrame };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/code-meta.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The bit after the language on a fence: ```` ```ts title="app/page.tsx" ````.
|
|
4
|
+
*
|
|
5
|
+
* Private — deliberately not an entry point. What a consumer sees is the
|
|
6
|
+
* `<figcaption>` it produces; the grammar is ours to extend.
|
|
7
|
+
*
|
|
8
|
+
* ## Read, never mutate
|
|
9
|
+
*
|
|
10
|
+
* `parseCodeMeta` takes a string and returns a value. It does not rewrite
|
|
11
|
+
* `code.data.meta`, and that restraint is the whole design: Shiki forwards the
|
|
12
|
+
* raw meta string to its transformers as `meta.__raw`, which is how
|
|
13
|
+
* `{1,3-5}` line highlighting and `showLineNumbers` will work when those land.
|
|
14
|
+
* A parser that consumed what it recognised would silently disable every
|
|
15
|
+
* feature it had not been taught about yet.
|
|
16
|
+
*
|
|
17
|
+
* ## Why `title=`
|
|
18
|
+
*
|
|
19
|
+
* Not a design question — a survey. Fumadocs, Starlight (via Expressive Code)
|
|
20
|
+
* and Docusaurus all spell it `title="…"`. Nextra's `filename=` and VitePress's
|
|
21
|
+
* `[filename]` are the minority, and `[…]` has nowhere to grow, so adopting it
|
|
22
|
+
* would mean two grammars in one meta string. `title` collides with frontmatter
|
|
23
|
+
* `title` as a word, which one comment fixes; making three of the five nearest
|
|
24
|
+
* comparators' users learn a second spelling does not have a fix.
|
|
25
|
+
*
|
|
26
|
+
* ## Unknown keys are ignored, malformed ones are not
|
|
27
|
+
*
|
|
28
|
+
* Shiki's meta string is an open namespace shared with its own transformers, so
|
|
29
|
+
* an author pasting `twoslash` or `{1,3}` from another site's documentation must
|
|
30
|
+
* not fail the build. But `title=app/page.tsx` — unquoted — is a *malformed*
|
|
31
|
+
* `title`, and ignoring it silently ships a caption reading `app/page.tsx` only
|
|
32
|
+
* up to the first space, or no caption at all. That throws, naming the file.
|
|
33
|
+
*/
|
|
34
|
+
/** What a fence's meta string asked for. */
|
|
35
|
+
interface CodeMeta {
|
|
36
|
+
/** The `<figcaption>` text, and part of the copy button's accessible name. */
|
|
37
|
+
title?: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read {@link CodeMeta} out of a fence's meta string.
|
|
41
|
+
*
|
|
42
|
+
* `path` is only ever used to name the offending document in an error. The
|
|
43
|
+
* alternative — an error saying `title=` was malformed, on a site with four
|
|
44
|
+
* hundred pages — is a grep.
|
|
45
|
+
*/
|
|
46
|
+
declare function parseCodeMeta(meta: string | undefined, path: string): CodeMeta;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { CodeMeta, parseCodeMeta };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { docsError } from "./docs-error.js";
|
|
2
|
+
//#region src/code-meta.ts
|
|
3
|
+
/**
|
|
4
|
+
* The bit after the language on a fence: ```` ```ts title="app/page.tsx" ````.
|
|
5
|
+
*
|
|
6
|
+
* Private — deliberately not an entry point. What a consumer sees is the
|
|
7
|
+
* `<figcaption>` it produces; the grammar is ours to extend.
|
|
8
|
+
*
|
|
9
|
+
* ## Read, never mutate
|
|
10
|
+
*
|
|
11
|
+
* `parseCodeMeta` takes a string and returns a value. It does not rewrite
|
|
12
|
+
* `code.data.meta`, and that restraint is the whole design: Shiki forwards the
|
|
13
|
+
* raw meta string to its transformers as `meta.__raw`, which is how
|
|
14
|
+
* `{1,3-5}` line highlighting and `showLineNumbers` will work when those land.
|
|
15
|
+
* A parser that consumed what it recognised would silently disable every
|
|
16
|
+
* feature it had not been taught about yet.
|
|
17
|
+
*
|
|
18
|
+
* ## Why `title=`
|
|
19
|
+
*
|
|
20
|
+
* Not a design question — a survey. Fumadocs, Starlight (via Expressive Code)
|
|
21
|
+
* and Docusaurus all spell it `title="…"`. Nextra's `filename=` and VitePress's
|
|
22
|
+
* `[filename]` are the minority, and `[…]` has nowhere to grow, so adopting it
|
|
23
|
+
* would mean two grammars in one meta string. `title` collides with frontmatter
|
|
24
|
+
* `title` as a word, which one comment fixes; making three of the five nearest
|
|
25
|
+
* comparators' users learn a second spelling does not have a fix.
|
|
26
|
+
*
|
|
27
|
+
* ## Unknown keys are ignored, malformed ones are not
|
|
28
|
+
*
|
|
29
|
+
* Shiki's meta string is an open namespace shared with its own transformers, so
|
|
30
|
+
* an author pasting `twoslash` or `{1,3}` from another site's documentation must
|
|
31
|
+
* not fail the build. But `title=app/page.tsx` — unquoted — is a *malformed*
|
|
32
|
+
* `title`, and ignoring it silently ships a caption reading `app/page.tsx` only
|
|
33
|
+
* up to the first space, or no caption at all. That throws, naming the file.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* `title="…"`, anywhere in the string, with the value in double quotes.
|
|
37
|
+
*
|
|
38
|
+
* A filename containing a double quote is inexpressible, and gets no escape
|
|
39
|
+
* mechanism: no real path has one, and an escape grammar is a permanent tax on
|
|
40
|
+
* every reader of this regex to serve a case that does not occur.
|
|
41
|
+
*/
|
|
42
|
+
const TITLE = /(^|\s)title="([^"]*)"/;
|
|
43
|
+
/**
|
|
44
|
+
* A `title=` at a word boundary, however it is spelled.
|
|
45
|
+
*
|
|
46
|
+
* Only consulted once {@link TITLE} has already failed, so reaching this means
|
|
47
|
+
* the author wrote a title the one grammar cannot read — unquoted,
|
|
48
|
+
* single-quoted, or opened and never closed. An earlier version excluded
|
|
49
|
+
* anything followed by a quote, which let `title="a.ts` (no closing quote)
|
|
50
|
+
* fall through and produce no caption at all, silently. That is the failure
|
|
51
|
+
* this function exists to make loud.
|
|
52
|
+
*/
|
|
53
|
+
const MALFORMED_TITLE = /(^|\s)title=/;
|
|
54
|
+
/**
|
|
55
|
+
* Read {@link CodeMeta} out of a fence's meta string.
|
|
56
|
+
*
|
|
57
|
+
* `path` is only ever used to name the offending document in an error. The
|
|
58
|
+
* alternative — an error saying `title=` was malformed, on a site with four
|
|
59
|
+
* hundred pages — is a grep.
|
|
60
|
+
*/
|
|
61
|
+
function parseCodeMeta(meta, path) {
|
|
62
|
+
if (meta === void 0 || meta.trim() === "") return {};
|
|
63
|
+
const matched = TITLE.exec(meta);
|
|
64
|
+
if (matched === null) {
|
|
65
|
+
if (MALFORMED_TITLE.test(meta)) throw docsError("invalid-code-meta", `${path}: a code fence has a malformed \`title\`. Write it as \`title="app/page.tsx"\`, with double quotes — an unquoted title stops at the first space, and a title containing a double quote cannot be expressed. Got: \`${meta.trim()}\``);
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
const title = matched[2];
|
|
69
|
+
return title === void 0 || title === "" ? {} : { title };
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { parseCodeMeta };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/docs-content-id.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The id of the rendered `<article>`, and the target of the skip link.
|
|
4
|
+
*
|
|
5
|
+
* Private — deliberately not an entry point in `package.json`. The documented
|
|
6
|
+
* import is `@waveso/docs/react/skip-link`, which re-exports it; this module
|
|
7
|
+
* exists so `src/next.ts` can have the constant without importing a
|
|
8
|
+
* `'use client'` module for a string.
|
|
9
|
+
*
|
|
10
|
+
* One constant for both halves, and no way to change it. The two files spelled
|
|
11
|
+
* it independently once, and a skip link pointing at an id nothing carries
|
|
12
|
+
* scrolls nowhere and focuses nothing — a failure with no symptom until a
|
|
13
|
+
* keyboard user hits it. `createDocsRoute` used to accept a `contentId` option
|
|
14
|
+
* that broke exactly that pairing, silently, because `SkipLink` had no matching
|
|
15
|
+
* option to follow it with.
|
|
16
|
+
*/
|
|
17
|
+
declare const DOCS_CONTENT_ID = "docs-content";
|
|
18
|
+
//#endregion
|
|
19
|
+
export { DOCS_CONTENT_ID };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/docs-content-id.ts
|
|
2
|
+
/**
|
|
3
|
+
* The id of the rendered `<article>`, and the target of the skip link.
|
|
4
|
+
*
|
|
5
|
+
* Private — deliberately not an entry point in `package.json`. The documented
|
|
6
|
+
* import is `@waveso/docs/react/skip-link`, which re-exports it; this module
|
|
7
|
+
* exists so `src/next.ts` can have the constant without importing a
|
|
8
|
+
* `'use client'` module for a string.
|
|
9
|
+
*
|
|
10
|
+
* One constant for both halves, and no way to change it. The two files spelled
|
|
11
|
+
* it independently once, and a skip link pointing at an id nothing carries
|
|
12
|
+
* scrolls nowhere and focuses nothing — a failure with no symptom until a
|
|
13
|
+
* keyboard user hits it. `createDocsRoute` used to accept a `contentId` option
|
|
14
|
+
* that broke exactly that pairing, silently, because `SkipLink` had no matching
|
|
15
|
+
* option to follow it with.
|
|
16
|
+
*/
|
|
17
|
+
const DOCS_CONTENT_ID = "docs-content";
|
|
18
|
+
//#endregion
|
|
19
|
+
export { DOCS_CONTENT_ID };
|
package/dist/docs-error.d.ts
CHANGED
|
@@ -1,58 +1,5 @@
|
|
|
1
|
+
import { DocsError, DocsErrorCode } from "./errors.js";
|
|
1
2
|
//#region src/docs-error.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* One error shape for the whole package.
|
|
4
|
-
*
|
|
5
|
-
* Private — deliberately not an entry point in `package.json`. Consumers get
|
|
6
|
-
* the `code` off the error they already catch; they do not import a class and
|
|
7
|
-
* they cannot `instanceof` against a copy of this module resolved twice.
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* What went wrong, as something a consumer can branch on.
|
|
11
|
-
*
|
|
12
|
-
* Before this existed, every failure was a bare `Error` and roughly half the
|
|
13
|
-
* messages omitted the package prefix, so a host wanting to downgrade (say)
|
|
14
|
-
* broken-link failures in dev had nothing to test but the message text — and
|
|
15
|
-
* `message.startsWith('@waveso/docs:')` was not even a reliable filter.
|
|
16
|
-
*/
|
|
17
|
-
type DocsErrorCode =
|
|
18
|
-
/** A link resolves to a route no published page owns. */
|
|
19
|
-
'broken-link' |
|
|
20
|
-
/** A link resolves to a page that exists but is `draft: true`. */
|
|
21
|
-
'draft-link' |
|
|
22
|
-
/** A link resolves to an alias, which is a redirect and not a page. */
|
|
23
|
-
'alias-link' |
|
|
24
|
-
/** An `aliases` entry is empty, escapes the root, or is not URL-safe. */
|
|
25
|
-
'invalid-alias' |
|
|
26
|
-
/** Two pages claim one alias, or an alias shadows a real route. */
|
|
27
|
-
'alias-collision' |
|
|
28
|
-
/** Two files resolve to the same route. */
|
|
29
|
-
'route-collision' |
|
|
30
|
-
/** A page's frontmatter is missing, malformed, or rejected by the schema. */
|
|
31
|
-
'invalid-frontmatter' |
|
|
32
|
-
/** A `meta.json` is malformed, or names something that is not there. */
|
|
33
|
-
'invalid-meta' |
|
|
34
|
-
/** An option passed to this package cannot be used as given. */
|
|
35
|
-
'invalid-config' |
|
|
36
|
-
/** `contentDir` does not point at a readable directory. */
|
|
37
|
-
'missing-content-dir' |
|
|
38
|
-
/** A markdown page is reachable only through a broken symbolic link. */
|
|
39
|
-
'broken-symlink' |
|
|
40
|
-
/** An `imageResolver` returned an unusable shape, threw, or was needed. */
|
|
41
|
-
'invalid-image' |
|
|
42
|
-
/** A theme name outside the supported set. */
|
|
43
|
-
'unknown-theme' |
|
|
44
|
-
/** A fence language outside the loaded set. */
|
|
45
|
-
'unknown-language' |
|
|
46
|
-
/** An optional peer (`next`) is absent or not the expected shape. */
|
|
47
|
-
'missing-peer' |
|
|
48
|
-
/** The search index could not be fetched or parsed. */
|
|
49
|
-
'search-index-unavailable' |
|
|
50
|
-
/** A plugin ran without the context this package always supplies. */
|
|
51
|
-
'internal';
|
|
52
|
-
/** An {@link Error} carrying a {@link DocsErrorCode}. */
|
|
53
|
-
interface DocsError extends Error {
|
|
54
|
-
readonly code: DocsErrorCode;
|
|
55
|
-
}
|
|
56
3
|
/**
|
|
57
4
|
* Build an error that names this package and says what class of thing failed.
|
|
58
5
|
*
|
|
@@ -68,7 +15,5 @@ interface DocsError extends Error {
|
|
|
68
15
|
* consumer does not care about, and also the ones a maintainer needs.
|
|
69
16
|
*/
|
|
70
17
|
declare function docsError(code: DocsErrorCode, message: string, options?: ErrorOptions): DocsError;
|
|
71
|
-
/** Narrow an unknown caught value to one of this package's errors. */
|
|
72
|
-
declare function isDocsError(value: unknown): value is DocsError;
|
|
73
18
|
//#endregion
|
|
74
|
-
export {
|
|
19
|
+
export { docsError };
|
package/dist/docs-error.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
+
import { DOCS_ERROR_PREFIX } from "./errors.js";
|
|
1
2
|
//#region src/docs-error.ts
|
|
2
3
|
/**
|
|
3
|
-
* One error shape for the whole package.
|
|
4
|
-
*
|
|
5
|
-
* Private — deliberately not an entry point in `package.json`. Consumers get
|
|
6
|
-
* the `code` off the error they already catch; they do not import a class and
|
|
7
|
-
* they cannot `instanceof` against a copy of this module resolved twice.
|
|
8
|
-
*/
|
|
9
|
-
/** Prefix every message carries, so a stack trace names the culprit package. */
|
|
10
|
-
const PREFIX = "@waveso/docs: ";
|
|
11
|
-
/**
|
|
12
4
|
* Build an error that names this package and says what class of thing failed.
|
|
13
5
|
*
|
|
14
6
|
* The message is passed through untouched apart from the prefix, which is added
|
|
@@ -23,7 +15,7 @@ const PREFIX = "@waveso/docs: ";
|
|
|
23
15
|
* consumer does not care about, and also the ones a maintainer needs.
|
|
24
16
|
*/
|
|
25
17
|
function docsError(code, message, options) {
|
|
26
|
-
const error = new Error(message.startsWith(
|
|
18
|
+
const error = new Error(message.startsWith("@waveso/docs: ") ? message : `${DOCS_ERROR_PREFIX}${message}`, options);
|
|
27
19
|
Object.defineProperty(error, "code", {
|
|
28
20
|
value: code,
|
|
29
21
|
enumerable: false,
|
|
@@ -32,9 +24,5 @@ function docsError(code, message, options) {
|
|
|
32
24
|
});
|
|
33
25
|
return error;
|
|
34
26
|
}
|
|
35
|
-
/** Narrow an unknown caught value to one of this package's errors. */
|
|
36
|
-
function isDocsError(value) {
|
|
37
|
-
return value instanceof Error && typeof value.code === "string" && value.message.startsWith(PREFIX);
|
|
38
|
-
}
|
|
39
27
|
//#endregion
|
|
40
|
-
export { docsError
|
|
28
|
+
export { docsError };
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//#region src/errors.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The error taxonomy, as public API.
|
|
4
|
+
*
|
|
5
|
+
* Every failure this package raises is an `Error` whose message names the
|
|
6
|
+
* package and which carries a `code` off this union. That exists so a host can
|
|
7
|
+
* branch — downgrade broken-link failures in a preview build, say, or report
|
|
8
|
+
* `invalid-frontmatter` differently from `missing-peer` — and until this
|
|
9
|
+
* module was an entry point there was no supported way to do it. The taxonomy
|
|
10
|
+
* said "branch on this" while the module said "you cannot import me", and the
|
|
11
|
+
* two paragraphs contradicted each other for nineteen codes and forty-five
|
|
12
|
+
* call sites.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { isDocsError } from '@waveso/docs/errors';
|
|
16
|
+
*
|
|
17
|
+
* try {
|
|
18
|
+
* await docs.renderAll();
|
|
19
|
+
* } catch (error) {
|
|
20
|
+
* if (isDocsError(error) && error.code === 'draft-link') {
|
|
21
|
+
* console.warn(error.message);
|
|
22
|
+
* } else {
|
|
23
|
+
* throw error;
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* No class is exported, and that is deliberate: `instanceof` against a copy of
|
|
29
|
+
* this module resolved twice — a monorepo with two versions installed, a
|
|
30
|
+
* bundler that duplicates it — silently answers `false`. A string `code` and a
|
|
31
|
+
* structural guard have no such failure mode.
|
|
32
|
+
*/
|
|
33
|
+
/** Prefix every message carries, so a stack trace names the culprit package. */
|
|
34
|
+
declare const DOCS_ERROR_PREFIX = "@waveso/docs: ";
|
|
35
|
+
/**
|
|
36
|
+
* What went wrong, as something a consumer can branch on.
|
|
37
|
+
*
|
|
38
|
+
* Before this existed, every failure was a bare `Error` and roughly half the
|
|
39
|
+
* messages omitted the package prefix, so a host wanting to downgrade (say)
|
|
40
|
+
* broken-link failures in dev had nothing to test but the message text — and
|
|
41
|
+
* `message.startsWith('@waveso/docs:')` was not even a reliable filter.
|
|
42
|
+
*/
|
|
43
|
+
type DocsErrorCode =
|
|
44
|
+
/** A link resolves to a route no published page owns. */
|
|
45
|
+
'broken-link' |
|
|
46
|
+
/** A link resolves to a page that exists but is `draft: true`. */
|
|
47
|
+
'draft-link' |
|
|
48
|
+
/** A link resolves to an alias, which is a redirect and not a page. */
|
|
49
|
+
'alias-link' |
|
|
50
|
+
/** An `aliases` entry is empty, escapes the root, or is not URL-safe. */
|
|
51
|
+
'invalid-alias' |
|
|
52
|
+
/** Two pages claim one alias, or an alias shadows a real route. */
|
|
53
|
+
'alias-collision' |
|
|
54
|
+
/** Two files resolve to the same route. */
|
|
55
|
+
'route-collision' |
|
|
56
|
+
/** A page's frontmatter is missing, malformed, or rejected by the schema. */
|
|
57
|
+
'invalid-frontmatter' |
|
|
58
|
+
/** A `meta.json` is malformed, or names something that is not there. */
|
|
59
|
+
'invalid-meta' |
|
|
60
|
+
/** An option passed to this package cannot be used as given. */
|
|
61
|
+
'invalid-config' |
|
|
62
|
+
/** `contentDir` does not point at a readable directory. */
|
|
63
|
+
'missing-content-dir' |
|
|
64
|
+
/** A markdown page is reachable only through a broken symbolic link. */
|
|
65
|
+
'broken-symlink' |
|
|
66
|
+
/** An `imageResolver` returned an unusable shape, threw, or was needed. */
|
|
67
|
+
'invalid-image' |
|
|
68
|
+
/** A theme name outside the supported set. */
|
|
69
|
+
'unknown-theme' |
|
|
70
|
+
/** A fence language outside the loaded set. */
|
|
71
|
+
'unknown-language' |
|
|
72
|
+
/** An optional peer (`next`) is absent or not the expected shape. */
|
|
73
|
+
'missing-peer' |
|
|
74
|
+
/** The search index could not be fetched or parsed. */
|
|
75
|
+
'search-index-unavailable' |
|
|
76
|
+
/** The search-index route ran at request time instead of being prerendered. */
|
|
77
|
+
'search-index-dynamic' |
|
|
78
|
+
/** A code fence's meta string has a `title` that cannot be read. */
|
|
79
|
+
'invalid-code-meta' |
|
|
80
|
+
/** A plugin ran without the context this package always supplies. */
|
|
81
|
+
'internal';
|
|
82
|
+
/** An {@link Error} carrying a {@link DocsErrorCode}. */
|
|
83
|
+
interface DocsError extends Error {
|
|
84
|
+
readonly code: DocsErrorCode;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Narrow an unknown caught value to one of this package's errors.
|
|
88
|
+
*
|
|
89
|
+
* Checks the prefix as well as the shape, so an unrelated `Error` that happens
|
|
90
|
+
* to carry a `code` — Node's own `ENOENT`, for one — is not mistaken for ours.
|
|
91
|
+
*/
|
|
92
|
+
declare function isDocsError(value: unknown): value is DocsError;
|
|
93
|
+
//#endregion
|
|
94
|
+
export { DOCS_ERROR_PREFIX, DocsError, DocsErrorCode, isDocsError };
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* The error taxonomy, as public API.
|
|
4
|
+
*
|
|
5
|
+
* Every failure this package raises is an `Error` whose message names the
|
|
6
|
+
* package and which carries a `code` off this union. That exists so a host can
|
|
7
|
+
* branch — downgrade broken-link failures in a preview build, say, or report
|
|
8
|
+
* `invalid-frontmatter` differently from `missing-peer` — and until this
|
|
9
|
+
* module was an entry point there was no supported way to do it. The taxonomy
|
|
10
|
+
* said "branch on this" while the module said "you cannot import me", and the
|
|
11
|
+
* two paragraphs contradicted each other for nineteen codes and forty-five
|
|
12
|
+
* call sites.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { isDocsError } from '@waveso/docs/errors';
|
|
16
|
+
*
|
|
17
|
+
* try {
|
|
18
|
+
* await docs.renderAll();
|
|
19
|
+
* } catch (error) {
|
|
20
|
+
* if (isDocsError(error) && error.code === 'draft-link') {
|
|
21
|
+
* console.warn(error.message);
|
|
22
|
+
* } else {
|
|
23
|
+
* throw error;
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* No class is exported, and that is deliberate: `instanceof` against a copy of
|
|
29
|
+
* this module resolved twice — a monorepo with two versions installed, a
|
|
30
|
+
* bundler that duplicates it — silently answers `false`. A string `code` and a
|
|
31
|
+
* structural guard have no such failure mode.
|
|
32
|
+
*/
|
|
33
|
+
/** Prefix every message carries, so a stack trace names the culprit package. */
|
|
34
|
+
const DOCS_ERROR_PREFIX = "@waveso/docs: ";
|
|
35
|
+
/**
|
|
36
|
+
* Narrow an unknown caught value to one of this package's errors.
|
|
37
|
+
*
|
|
38
|
+
* Checks the prefix as well as the shape, so an unrelated `Error` that happens
|
|
39
|
+
* to carry a `code` — Node's own `ENOENT`, for one — is not mistaken for ours.
|
|
40
|
+
*/
|
|
41
|
+
function isDocsError(value) {
|
|
42
|
+
return value instanceof Error && typeof value.code === "string" && value.message.startsWith("@waveso/docs: ");
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { DOCS_ERROR_PREFIX, isDocsError };
|