@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +72 -16
  3. package/bin/ursa.js +14 -1
  4. package/meta/templates/default-template/menu.js +18 -1
  5. package/meta/templates/default-template/search.js +11 -0
  6. package/meta/templates/default-template/widgets.js +4 -0
  7. package/package.json +1 -2
  8. package/src/dev.js +13 -23
  9. package/src/helper/__test__/contentHash.test.js +16 -6
  10. package/src/helper/assetBundler.js +93 -19
  11. package/src/helper/automenu.js +36 -11
  12. package/src/helper/build/__test__/autoIndex.test.js +2 -132
  13. package/src/helper/build/__test__/graph.test.js +259 -3
  14. package/src/helper/build/__test__/pass.test.js +553 -0
  15. package/src/helper/build/autoIndex.js +2 -371
  16. package/src/helper/build/excludeFilter.js +1 -2
  17. package/src/helper/build/footer.js +27 -14
  18. package/src/helper/build/graph.js +575 -152
  19. package/src/helper/build/index.js +0 -2
  20. package/src/helper/build/metadata.js +19 -5
  21. package/src/helper/build/pass.js +497 -0
  22. package/src/helper/build/precedence.js +174 -0
  23. package/src/helper/build/site.js +1270 -0
  24. package/src/helper/build/templates.js +1 -2
  25. package/src/helper/build/tracedFs.js +247 -0
  26. package/src/helper/contentHash.js +0 -78
  27. package/src/helper/customMenu.js +1 -1
  28. package/src/helper/fileRenderer.js +119 -111
  29. package/src/helper/findScriptJs.js +1 -1
  30. package/src/helper/findStyleCss.js +1 -1
  31. package/src/helper/folderConfig.js +7 -18
  32. package/src/helper/fullTextIndex.js +41 -29
  33. package/src/helper/imageProcessor.js +45 -0
  34. package/src/helper/linkValidator.js +118 -127
  35. package/src/helper/mdxRenderer.js +27 -5
  36. package/src/helper/menuLabels.js +30 -5
  37. package/src/helper/whitelistFilter.js +1 -2
  38. package/src/jobs/generate.js +67 -1829
  39. package/src/serve.js +317 -697
  40. package/src/helper/__test__/dependencyTracker.test.js +0 -157
  41. package/src/helper/build/cacheBust.js +0 -141
  42. package/src/helper/build/navCache.js +0 -145
  43. package/src/helper/build/watchCache.js +0 -33
  44. 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
+ }