@kenjura/ursa 0.96.0 → 0.97.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 +26 -0
- package/README.md +72 -16
- package/bin/ursa.js +14 -1
- package/meta/templates/default-template/menu.js +18 -1
- package/meta/templates/default-template/search.js +11 -0
- package/meta/templates/default-template/widgets.js +4 -0
- package/package.json +1 -2
- package/src/dev.js +13 -23
- package/src/helper/__test__/contentHash.test.js +16 -6
- package/src/helper/assetBundler.js +93 -19
- package/src/helper/automenu.js +36 -11
- package/src/helper/build/__test__/autoIndex.test.js +2 -132
- package/src/helper/build/__test__/graph.test.js +259 -3
- package/src/helper/build/__test__/pass.test.js +553 -0
- package/src/helper/build/autoIndex.js +2 -371
- package/src/helper/build/excludeFilter.js +1 -2
- package/src/helper/build/footer.js +27 -14
- package/src/helper/build/graph.js +575 -152
- package/src/helper/build/index.js +0 -2
- package/src/helper/build/metadata.js +19 -5
- package/src/helper/build/pass.js +497 -0
- package/src/helper/build/precedence.js +174 -0
- package/src/helper/build/site.js +1270 -0
- package/src/helper/build/templates.js +1 -2
- package/src/helper/build/tracedFs.js +247 -0
- package/src/helper/contentHash.js +0 -78
- package/src/helper/customMenu.js +1 -1
- package/src/helper/fileRenderer.js +119 -111
- package/src/helper/findScriptJs.js +1 -1
- package/src/helper/findStyleCss.js +1 -1
- package/src/helper/folderConfig.js +7 -18
- package/src/helper/fullTextIndex.js +41 -29
- package/src/helper/imageProcessor.js +45 -0
- package/src/helper/linkValidator.js +118 -127
- package/src/helper/mdxRenderer.js +27 -5
- package/src/helper/menuLabels.js +30 -5
- package/src/helper/whitelistFilter.js +1 -2
- package/src/jobs/generate.js +67 -1829
- package/src/serve.js +317 -697
- package/src/helper/__test__/dependencyTracker.test.js +0 -157
- package/src/helper/build/cacheBust.js +0 -141
- package/src/helper/build/navCache.js +0 -145
- package/src/helper/build/watchCache.js +0 -33
- package/src/helper/dependencyTracker.js +0 -384
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one copy of ursa's path rules.
|
|
3
|
+
*
|
|
4
|
+
* Several sources can claim one output path (`index.md` beside `index.mdx`;
|
|
5
|
+
* `foo.md` beside a hand-written `foo.html`; `home.md` beside a folder-named
|
|
6
|
+
* `foo/foo.md`), and several different lists of who wins used to live in the
|
|
7
|
+
* link validator, the auto-index generator, the menu builder, the dev server
|
|
8
|
+
* and the single-file regenerator — and they disagreed. This module is the
|
|
9
|
+
* list. Link resolution, output ownership (`outputOwner` in site.js), the menu,
|
|
10
|
+
* `dev` and the dev server's URL resolver all read it from here.
|
|
11
|
+
*
|
|
12
|
+
* See docs/SERVE.md §8.2 and §8.3.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { basename, dirname, extname, posix } from "path";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Article extensions in precedence order, highest first. `.mdx` beats `.md`
|
|
19
|
+
* because the migration direction is md → mdx, and during the overlap the
|
|
20
|
+
* author expects the new file to show.
|
|
21
|
+
*/
|
|
22
|
+
export const ARTICLE_EXTENSIONS = [".mdx", ".md", ".txt", ".yml"];
|
|
23
|
+
|
|
24
|
+
/** A hand-written `.html` in the source tree outranks any rendered document. */
|
|
25
|
+
export const HANDWRITTEN_EXTENSION = ".html";
|
|
26
|
+
|
|
27
|
+
/** Everything that renders to (or is copied as) a page. */
|
|
28
|
+
export const PAGE_SOURCE_EXTENSIONS = [HANDWRITTEN_EXTENSION, ...ARTICLE_EXTENSIONS];
|
|
29
|
+
|
|
30
|
+
export const ARTICLE_EXT_RE = /\.(mdx|md|txt|yml)$/i;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Basenames that can stand in for a folder's `index`, in precedence order.
|
|
34
|
+
* The folder's own name (`foo/foo.md`) comes after all of these; the generated
|
|
35
|
+
* auto-index comes last.
|
|
36
|
+
*/
|
|
37
|
+
export const INDEX_BASENAMES = ["index", "_index", "home", "_home"];
|
|
38
|
+
|
|
39
|
+
/** Marker returned by `indexCandidates` after every document candidate. */
|
|
40
|
+
export const AUTO_INDEX = Symbol("auto-index");
|
|
41
|
+
|
|
42
|
+
export function isArticle(path) {
|
|
43
|
+
return ARTICLE_EXT_RE.test(path);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isHandwrittenHtml(path) {
|
|
47
|
+
return extname(path).toLowerCase() === HANDWRITTEN_EXTENSION;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** True when a basename is one of the folder-index names. */
|
|
51
|
+
export function isIndexBasename(base) {
|
|
52
|
+
return INDEX_BASENAMES.includes(base);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Source paths (relative to the docroot) that could produce the output
|
|
57
|
+
* `<dir>/<base>.html`, highest precedence first. Does not include the
|
|
58
|
+
* folder-index alternates: use `indexCandidates` for `<dir>/index.html`.
|
|
59
|
+
* @param {string} dirRel - Directory relative to the docroot ("" for the root)
|
|
60
|
+
* @param {string} base - Output basename without extension
|
|
61
|
+
* @returns {string[]}
|
|
62
|
+
*/
|
|
63
|
+
export function pageCandidates(dirRel, base) {
|
|
64
|
+
const prefix = dirRel ? `${dirRel}/` : "";
|
|
65
|
+
return PAGE_SOURCE_EXTENSIONS.map((ext) => `${prefix}${base}${ext}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Everything that could own `<dirRel>/index.html`, highest precedence first,
|
|
70
|
+
* ending with the AUTO_INDEX marker:
|
|
71
|
+
* 1. index.html (hand-written)
|
|
72
|
+
* 2. index.mdx, index.md, index.txt, index.yml
|
|
73
|
+
* 3. _index.* in the same extension order
|
|
74
|
+
* 4. home.*, then _home.*
|
|
75
|
+
* 5. <foldername>.* (folder-named promotion)
|
|
76
|
+
* 6. the generated auto-index
|
|
77
|
+
* @param {string} dirRel - Directory relative to the docroot ("" for the root)
|
|
78
|
+
* @returns {(string|symbol)[]}
|
|
79
|
+
*/
|
|
80
|
+
export function indexCandidates(dirRel) {
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const base of INDEX_BASENAMES) out.push(...pageCandidates(dirRel, base));
|
|
83
|
+
const folderName = dirRel ? basename(dirRel) : null;
|
|
84
|
+
if (folderName && !isIndexBasename(folderName)) {
|
|
85
|
+
out.push(...pageCandidates(dirRel, folderName));
|
|
86
|
+
}
|
|
87
|
+
out.push(AUTO_INDEX);
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Candidates for any output path relative to the output directory.
|
|
93
|
+
* `foo/index.html` → indexCandidates("foo"); `foo/bar.html` → pageCandidates("foo", "bar").
|
|
94
|
+
* @param {string} outputRel - e.g. "foo/bar.html"
|
|
95
|
+
* @returns {(string|symbol)[]}
|
|
96
|
+
*/
|
|
97
|
+
export function candidatesForOutput(outputRel) {
|
|
98
|
+
const ext = extname(outputRel);
|
|
99
|
+
const base = basename(outputRel, ext);
|
|
100
|
+
const dir = dirname(outputRel);
|
|
101
|
+
const dirRel = dir === "." ? "" : dir;
|
|
102
|
+
if (base === "index" && ext === ".html") return indexCandidates(dirRel);
|
|
103
|
+
return pageCandidates(dirRel, base);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The output path (relative to the output directory) a source document
|
|
108
|
+
* renders to on its own account: `foo/bar.md` → `foo/bar.html`.
|
|
109
|
+
* Folder-index promotion (`foo/foo.md` → `foo/index.html`) is a second
|
|
110
|
+
* output decided by ownership, not by this function.
|
|
111
|
+
*/
|
|
112
|
+
export function outputPathFor(sourceRel) {
|
|
113
|
+
return sourceRel.replace(/\.[^./]+$/, ".html");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* True when a document named like this could be promoted to its folder's
|
|
118
|
+
* index (it is an index alternate or is named after its folder).
|
|
119
|
+
*/
|
|
120
|
+
export function isIndexCandidate(sourceRel) {
|
|
121
|
+
const ext = extname(sourceRel);
|
|
122
|
+
const base = basename(sourceRel, ext);
|
|
123
|
+
if (isIndexBasename(base)) return true;
|
|
124
|
+
const dir = dirname(sourceRel);
|
|
125
|
+
return dir !== "." && basename(dir) === base;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve a request URL path to the output file it names, relative to the
|
|
130
|
+
* output directory. Shared by the dev server's middleware and its reload
|
|
131
|
+
* logic so they cannot disagree.
|
|
132
|
+
*
|
|
133
|
+
* /foo/ → foo/index.html
|
|
134
|
+
* /foo.html → foo.html
|
|
135
|
+
* /foo → foo.html if that output exists, else foo/index.html (the file wins)
|
|
136
|
+
*
|
|
137
|
+
* @param {string} urlPath - Path component of the request URL (no query)
|
|
138
|
+
* @param {(outputRel: string) => boolean} outputExists - Whether an output file exists
|
|
139
|
+
* @returns {string} Output path relative to the output directory
|
|
140
|
+
*/
|
|
141
|
+
export function resolveUrlToOutput(urlPath, outputExists) {
|
|
142
|
+
let p;
|
|
143
|
+
try {
|
|
144
|
+
p = decodeURIComponent(urlPath.split("?")[0].split("#")[0]);
|
|
145
|
+
} catch {
|
|
146
|
+
p = urlPath;
|
|
147
|
+
}
|
|
148
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
149
|
+
p = posix.normalize(p);
|
|
150
|
+
if (p === "/" || p.endsWith("/")) return posix.join(p, "index.html").replace(/^\//, "");
|
|
151
|
+
const rel = p.replace(/^\//, "");
|
|
152
|
+
if (extname(rel)) return rel;
|
|
153
|
+
if (outputExists(rel + ".html")) return rel + ".html";
|
|
154
|
+
return posix.join(rel, "index.html");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Normalize a client-reported URL for comparison with output paths.
|
|
159
|
+
* Both `/foo/` and `/foo/index.html` name the same output.
|
|
160
|
+
*/
|
|
161
|
+
export function urlToOutputCandidates(urlPath) {
|
|
162
|
+
let p;
|
|
163
|
+
try {
|
|
164
|
+
p = decodeURIComponent(urlPath.split("?")[0].split("#")[0]);
|
|
165
|
+
} catch {
|
|
166
|
+
p = urlPath;
|
|
167
|
+
}
|
|
168
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
169
|
+
p = posix.normalize(p);
|
|
170
|
+
const rel = p.replace(/^\//, "");
|
|
171
|
+
if (p === "/" || p.endsWith("/")) return [posix.join(rel, "index.html")];
|
|
172
|
+
if (extname(rel)) return [rel];
|
|
173
|
+
return [rel + ".html", posix.join(rel, "index.html")];
|
|
174
|
+
}
|