@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,34 @@
|
|
|
1
|
+
//#region src/sitemap-limit.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Google's per-sitemap URL cap, and the warning for crossing it.
|
|
4
|
+
*
|
|
5
|
+
* Private — deliberately not an entry point in `package.json`. It lives in its
|
|
6
|
+
* own module for one reason: the branch is otherwise untestable. `next.ts` held
|
|
7
|
+
* the limit and the `console.warn` inline, and reaching them from a test meant
|
|
8
|
+
* writing 50,001 markdown files to a temporary directory, so the test that
|
|
9
|
+
* claimed to cover it ("warns rather than silently emitting an oversized
|
|
10
|
+
* sitemap") built a one-page site and asserted the warning did *not* fire. It
|
|
11
|
+
* could only ever fail if the comparison were inverted.
|
|
12
|
+
*
|
|
13
|
+
* A count is the whole input. Split out, the arithmetic and the wording are
|
|
14
|
+
* checkable in microseconds, and `createDocsSitemap`'s own test keeps covering
|
|
15
|
+
* the case that matters at the integration level: an ordinary sitemap stays
|
|
16
|
+
* quiet.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The cap.
|
|
20
|
+
*
|
|
21
|
+
* 50,000 URLs or 50 MB uncompressed, whichever comes first; a crawler rejects
|
|
22
|
+
* the file whole rather than truncating it.
|
|
23
|
+
*/
|
|
24
|
+
declare const SITEMAP_URL_LIMIT = 50000;
|
|
25
|
+
/**
|
|
26
|
+
* The warning for a sitemap of `count` URLs, or `undefined` when it fits.
|
|
27
|
+
*
|
|
28
|
+
* Splitting belongs to the caller — Next's `generateSitemaps` plus a slice of
|
|
29
|
+
* the returned array is three lines — but silently emitting a file no crawler
|
|
30
|
+
* will read is not something to discover from Search Console six weeks later.
|
|
31
|
+
*/
|
|
32
|
+
declare function sitemapLimitWarning(count: number): string | undefined;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { SITEMAP_URL_LIMIT, sitemapLimitWarning };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/sitemap-limit.ts
|
|
2
|
+
/**
|
|
3
|
+
* Google's per-sitemap URL cap, and the warning for crossing it.
|
|
4
|
+
*
|
|
5
|
+
* Private — deliberately not an entry point in `package.json`. It lives in its
|
|
6
|
+
* own module for one reason: the branch is otherwise untestable. `next.ts` held
|
|
7
|
+
* the limit and the `console.warn` inline, and reaching them from a test meant
|
|
8
|
+
* writing 50,001 markdown files to a temporary directory, so the test that
|
|
9
|
+
* claimed to cover it ("warns rather than silently emitting an oversized
|
|
10
|
+
* sitemap") built a one-page site and asserted the warning did *not* fire. It
|
|
11
|
+
* could only ever fail if the comparison were inverted.
|
|
12
|
+
*
|
|
13
|
+
* A count is the whole input. Split out, the arithmetic and the wording are
|
|
14
|
+
* checkable in microseconds, and `createDocsSitemap`'s own test keeps covering
|
|
15
|
+
* the case that matters at the integration level: an ordinary sitemap stays
|
|
16
|
+
* quiet.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The cap.
|
|
20
|
+
*
|
|
21
|
+
* 50,000 URLs or 50 MB uncompressed, whichever comes first; a crawler rejects
|
|
22
|
+
* the file whole rather than truncating it.
|
|
23
|
+
*/
|
|
24
|
+
const SITEMAP_URL_LIMIT = 5e4;
|
|
25
|
+
/**
|
|
26
|
+
* The warning for a sitemap of `count` URLs, or `undefined` when it fits.
|
|
27
|
+
*
|
|
28
|
+
* Splitting belongs to the caller — Next's `generateSitemaps` plus a slice of
|
|
29
|
+
* the returned array is three lines — but silently emitting a file no crawler
|
|
30
|
+
* will read is not something to discover from Search Console six weeks later.
|
|
31
|
+
*/
|
|
32
|
+
function sitemapLimitWarning(count) {
|
|
33
|
+
if (count <= 5e4) return void 0;
|
|
34
|
+
return `@waveso/docs: this sitemap has ${count} URLs, above Google's limit of ${SITEMAP_URL_LIMIT}. Split it with Next's \`generateSitemaps\` and slice the array this returns.`;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { SITEMAP_URL_LIMIT, sitemapLimitWarning };
|
package/dist/source.d.ts
CHANGED
|
@@ -53,27 +53,5 @@ declare function resolveDocsConfig<TFrontmatter extends DocFrontmatter = DocFron
|
|
|
53
53
|
* Create (or reuse) the source for a content directory.
|
|
54
54
|
*/
|
|
55
55
|
declare function createDocsSource<TFrontmatter extends DocFrontmatter = DocFrontmatter>(config: DocsConfig<TFrontmatter>): DocsSource<TFrontmatter>;
|
|
56
|
-
/**
|
|
57
|
-
* A former URL from `aliases` frontmatter, as a route.
|
|
58
|
-
*
|
|
59
|
-
* `'quickstart'` on a site mounted at `/docs` becomes `/docs/quickstart`.
|
|
60
|
-
* Leading and trailing slashes are tolerated because authors write them, but
|
|
61
|
-
* the value is always relative to the base path — an alias of `'/docs/old'` on
|
|
62
|
-
* a `/docs` site would produce `/docs/docs/old`.
|
|
63
|
-
*
|
|
64
|
-
* Shared by both adapters so they agree on which routes exist: an alias is a
|
|
65
|
-
* redirect the host installs, so a link to one resolves, and a link that
|
|
66
|
-
* builds under Next must build under Vite. The source scan calls it too, so
|
|
67
|
-
* every rejection below names the markdown file at the moment it is read.
|
|
68
|
-
*/
|
|
69
|
-
declare function toAliasRoute(alias: string, basePath: string,
|
|
70
|
-
/**
|
|
71
|
-
* The source path, for the error. A STRING rather than the whole `DocFile`
|
|
72
|
-
* it used to take: this function dereferenced exactly one property of it, and
|
|
73
|
-
* demanding the object meant a cache reader or a manifest-driven redirect
|
|
74
|
-
* table had to fabricate a `DocFile` to agree with the package about which
|
|
75
|
-
* routes exist. That is the reason it is exported at all.
|
|
76
|
-
*/
|
|
77
|
-
sourceLabel: string): string;
|
|
78
56
|
//#endregion
|
|
79
|
-
export { DocsSource, createDocsSource, resolveDocsConfig
|
|
57
|
+
export { DocsSource, createDocsSource, resolveDocsConfig };
|
package/dist/source.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { docsError } from "./docs-error.js";
|
|
2
2
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
3
3
|
import { orderNavEntries, readDocsMeta } from "./meta.js";
|
|
4
|
-
import {
|
|
4
|
+
import { encodeSegments, toAliasRoute } from "./route-path.js";
|
|
5
5
|
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import
|
|
7
|
+
import { VFile } from "vfile";
|
|
8
|
+
import { matter } from "vfile-matter";
|
|
8
9
|
//#region src/source.ts
|
|
9
10
|
/** Markdown only. MDX is deliberately out of scope for this package. */
|
|
10
11
|
const PAGE_EXTENSION = ".md";
|
|
@@ -20,7 +21,11 @@ const INDEX_NAME = "index";
|
|
|
20
21
|
*/
|
|
21
22
|
function resolveDocsConfig(config) {
|
|
22
23
|
return {
|
|
23
|
-
contentDir: path.resolve(
|
|
24
|
+
contentDir: path.resolve(
|
|
25
|
+
/*turbopackIgnore: true*/
|
|
26
|
+
process.cwd(),
|
|
27
|
+
config.contentDir
|
|
28
|
+
),
|
|
24
29
|
basePath: normalizeBasePath(config.basePath ?? "/docs"),
|
|
25
30
|
includeDrafts: config.includeDrafts ?? false,
|
|
26
31
|
assertLinks: config.assertLinks ?? true,
|
|
@@ -87,6 +92,33 @@ function buildSource(config) {
|
|
|
87
92
|
const isVisible = (file) => config.includeDrafts || file.frontmatter.draft !== true;
|
|
88
93
|
return {
|
|
89
94
|
config,
|
|
95
|
+
/**
|
|
96
|
+
* Throw the scan away. The next query reads the disk again.
|
|
97
|
+
*
|
|
98
|
+
* ⚠️ A STAT-WALK DIRTY CHECK WAS BUILT HERE AND MEASURED AND REMOVED. The
|
|
99
|
+
* idea is obvious and the roadmap called for it: mark dirty, then compare
|
|
100
|
+
* a stat-only fingerprint of the tree against the cached one and skip the
|
|
101
|
+
* re-read when nothing changed. It rests on stat being much cheaper than
|
|
102
|
+
* read, and on this corpus it is not — the fingerprint has to `readdir`
|
|
103
|
+
* every directory and `stat` every file, which is nearly everything the
|
|
104
|
+
* scan does apart from the read and the parse.
|
|
105
|
+
*
|
|
106
|
+
* Measured over 501 pages, median of six, against a full rescan:
|
|
107
|
+
*
|
|
108
|
+
* ~1.4 KB pages 28.3 ms vs 26.8 ms 0.95x (slower)
|
|
109
|
+
* ~20 KB pages 28.8 ms vs 27.4 ms 0.95x (slower)
|
|
110
|
+
* ~120 KB pages 39.1 ms vs 45.1 ms 1.15x (faster)
|
|
111
|
+
*
|
|
112
|
+
* Documentation pages are the first two rows. So it is a small regression
|
|
113
|
+
* plus a new class of invalidation bug, in exchange for a win on a corpus
|
|
114
|
+
* nobody has. There is no cheaper correct fingerprint either: statting
|
|
115
|
+
* only directories catches an added or renamed file but not an edited one,
|
|
116
|
+
* which is the common case in a dev server.
|
|
117
|
+
*
|
|
118
|
+
* If this is ever revisited, the thing to change is the *scan*, not the
|
|
119
|
+
* check — patch only the files whose mtime moved and re-derive the nav in
|
|
120
|
+
* memory, which is a different item with a much harder correctness story.
|
|
121
|
+
*/
|
|
90
122
|
invalidate() {
|
|
91
123
|
cached = null;
|
|
92
124
|
},
|
|
@@ -201,9 +233,10 @@ async function readPage(filePath, name, dirSegments, config) {
|
|
|
201
233
|
let data;
|
|
202
234
|
let content;
|
|
203
235
|
try {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
236
|
+
const file = new VFile({ value: raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw });
|
|
237
|
+
matter(file, { strip: true });
|
|
238
|
+
data = file.data.matter;
|
|
239
|
+
content = String(file);
|
|
207
240
|
} catch (err) {
|
|
208
241
|
const reason = err instanceof Error ? err.message : String(err);
|
|
209
242
|
throw docsError("invalid-frontmatter", `Could not parse the frontmatter block in ${relativePath}: ${reason}`, { cause: err });
|
|
@@ -348,29 +381,6 @@ function isVisibleIn(file, config) {
|
|
|
348
381
|
* it. `c++` is the loud sibling: the build aborts with `Unexpected MODIFIER at
|
|
349
382
|
* 7`, naming an offset into a string the author never wrote and no file.
|
|
350
383
|
*/
|
|
351
|
-
const ALIAS_PATTERN_CHARS = /[:()+*?{}]/;
|
|
352
|
-
/**
|
|
353
|
-
* A former URL from `aliases` frontmatter, as a route.
|
|
354
|
-
*
|
|
355
|
-
* `'quickstart'` on a site mounted at `/docs` becomes `/docs/quickstart`.
|
|
356
|
-
* Leading and trailing slashes are tolerated because authors write them, but
|
|
357
|
-
* the value is always relative to the base path — an alias of `'/docs/old'` on
|
|
358
|
-
* a `/docs` site would produce `/docs/docs/old`.
|
|
359
|
-
*
|
|
360
|
-
* Shared by both adapters so they agree on which routes exist: an alias is a
|
|
361
|
-
* redirect the host installs, so a link to one resolves, and a link that
|
|
362
|
-
* builds under Next must build under Vite. The source scan calls it too, so
|
|
363
|
-
* every rejection below names the markdown file at the moment it is read.
|
|
364
|
-
*/
|
|
365
|
-
function toAliasRoute(alias, basePath, sourceLabel) {
|
|
366
|
-
const trimmed = alias.trim();
|
|
367
|
-
if (trimmed.split("/").some((part) => part === "." || part === "..")) throw docsError("invalid-alias", `@waveso/docs: the alias '${alias}' in ${sourceLabel} has a '.' or '..' segment. An alias is a former URL relative to the docs base path, not a path on disk: write \`aliases: [legacy/old-name]\`.`);
|
|
368
|
-
const pattern = ALIAS_PATTERN_CHARS.exec(trimmed);
|
|
369
|
-
if (pattern !== null) throw docsError("invalid-alias", `@waveso/docs: the alias '${alias}' in ${sourceLabel} contains '${pattern[0]}', which Next compiles as redirect pattern syntax rather than as part of the URL — the redirect then swallows every page whose route the pattern happens to match, or fails the build. Remove the character; an alias is a literal former URL.`);
|
|
370
|
-
const segments = foldSegments([], trimmed);
|
|
371
|
-
if (segments === void 0 || segments.length === 0) throw docsError("invalid-alias", `@waveso/docs: ${sourceLabel} has an empty entry in its \`aliases\` frontmatter. Each alias is a former URL for this page, relative to the docs base path — e.g. \`aliases: [quickstart]\`.`);
|
|
372
|
-
return `${basePath}/${encodeSegments(segments)}`;
|
|
373
|
-
}
|
|
374
384
|
/**
|
|
375
385
|
* ⚠️ `_` AND `.` BOTH, MATCHING `isIgnoredDir` BELOW — which is what this did
|
|
376
386
|
* NOT do. A leading dot was skipped and a leading underscore was not, so
|
|
@@ -394,19 +404,6 @@ function isIgnoredDir(name) {
|
|
|
394
404
|
function stripExtension(name) {
|
|
395
405
|
return name.slice(0, name.length - path.extname(name).length);
|
|
396
406
|
}
|
|
397
|
-
/**
|
|
398
|
-
* Percent-encode the segments, and only here.
|
|
399
|
-
*
|
|
400
|
-
* `segments` and `slug` stay raw on purpose: Next decodes route params before
|
|
401
|
-
* they reach `find()`, so an encoded slug would match nothing. Unencoded, a
|
|
402
|
-
* `#`, `?` or `%` in a filename stops being part of the path — the sitemap
|
|
403
|
-
* emitted `https://example.com/docs/c#%20guide`, and `alternates.canonical`
|
|
404
|
-
* and `og:url` are built by the same call — while a space produced a URL that
|
|
405
|
-
* only works until something re-encodes it.
|
|
406
|
-
*/
|
|
407
|
-
function encodeSegments(segments) {
|
|
408
|
-
return segments.map(encodeURIComponent).join("/");
|
|
409
|
-
}
|
|
410
407
|
function toHref(basePath, segments) {
|
|
411
408
|
if (segments.length === 0) return basePath === "" ? "/" : basePath;
|
|
412
409
|
return `${basePath}/${encodeSegments(segments)}`;
|
|
@@ -425,4 +422,4 @@ function humanize(name) {
|
|
|
425
422
|
return name.split(/[-_\s]+/).filter((word) => word !== "").map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(" ");
|
|
426
423
|
}
|
|
427
424
|
//#endregion
|
|
428
|
-
export { createDocsSource, resolveDocsConfig
|
|
425
|
+
export { createDocsSource, resolveDocsConfig };
|