@dogsbay/ssg 0.2.0-beta.131 → 0.2.0-beta.133
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/dist/package/THIRD-PARTY-NOTICES.txt +5 -5
- package/dist/package/browser.mjs +300 -43
- package/dist/package/types/core/build.d.ts +30 -0
- package/dist/package/types/core/dev.d.ts +18 -6
- package/dist/package/types/core/nav.d.ts +2 -1
- package/dist/package/types/core/preprocess.d.ts +54 -1
- package/dist/package/types/core/scan-cache.d.ts +51 -0
- package/dist/package/types/package/browser.d.ts +2 -2
- package/package.json +29 -29
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
Third-party software in @dogsbay/ssg (dist/package/)
|
|
2
2
|
|
|
3
3
|
========================================================================
|
|
4
|
-
@dogsbay/elements@0.2.0-beta.
|
|
4
|
+
@dogsbay/elements@0.2.0-beta.133 (MIT)
|
|
5
5
|
https://github.com/dogsbay/dogsbay.git
|
|
6
6
|
|
|
7
7
|
Licensed under MIT; the package ships no licence file.
|
|
8
8
|
|
|
9
9
|
========================================================================
|
|
10
|
-
@dogsbay/primitives@0.2.0-beta.
|
|
10
|
+
@dogsbay/primitives@0.2.0-beta.133 (MIT)
|
|
11
11
|
https://github.com/dogsbay/dogsbay.git
|
|
12
12
|
|
|
13
13
|
Licensed under MIT; the package ships no licence file.
|
|
14
14
|
|
|
15
15
|
========================================================================
|
|
16
|
-
@dogsbay/render-html@0.2.0-beta.
|
|
16
|
+
@dogsbay/render-html@0.2.0-beta.133 (MIT)
|
|
17
17
|
https://github.com/dogsbay/dogsbay.git
|
|
18
18
|
|
|
19
19
|
Licensed under MIT; the package ships no licence file.
|
|
20
20
|
|
|
21
21
|
========================================================================
|
|
22
|
-
@dogsbay/search@0.2.0-beta.
|
|
22
|
+
@dogsbay/search@0.2.0-beta.133 (MIT)
|
|
23
23
|
https://github.com/dogsbay/dogsbay.git
|
|
24
24
|
|
|
25
25
|
Licensed under MIT; the package ships no licence file.
|
|
26
26
|
|
|
27
27
|
========================================================================
|
|
28
|
-
@dogsbay/ui@0.2.0-beta.
|
|
28
|
+
@dogsbay/ui@0.2.0-beta.133 (MIT)
|
|
29
29
|
https://github.com/dogsbay/dogsbay.git
|
|
30
30
|
|
|
31
31
|
Licensed under MIT; the package ships no licence file.
|
package/dist/package/browser.mjs
CHANGED
|
@@ -462,6 +462,12 @@ function sourceNav(v, path, from) {
|
|
|
462
462
|
if (!/\.(ya?ml|json)$/i.test(p)) throw new ConfigError(`content.sources (${path}): nav must be a .topicmap.yml topic map or a .yml or .json nav file; got ${JSON.stringify(v)}`);
|
|
463
463
|
return p;
|
|
464
464
|
}
|
|
465
|
+
function projectRoots(cfg) {
|
|
466
|
+
const roots = cfg.sources.map((s) => s.path === "." ? "" : s.path);
|
|
467
|
+
if (roots.includes("")) return [""];
|
|
468
|
+
const navDirs = cfg.sources.flatMap((s) => s.nav ? [s.nav.includes("/") ? s.nav.slice(0, s.nav.lastIndexOf("/")) : s.nav] : []);
|
|
469
|
+
return [.../* @__PURE__ */ new Set([...roots, cfg.themeDir, ...cfg.css, ...navDirs])].filter(Boolean);
|
|
470
|
+
}
|
|
465
471
|
function sourceRoot(s) {
|
|
466
472
|
return s.path === "." ? "" : s.path.replace(/^\.\//, "").replace(/\/+$/, "") + "/";
|
|
467
473
|
}
|
|
@@ -548,8 +554,8 @@ function slugFromPath(p) {
|
|
|
548
554
|
if (s.endsWith("/index")) s = s.slice(0, -"/index".length);
|
|
549
555
|
return s;
|
|
550
556
|
}
|
|
551
|
-
function parseNavYaml(text, labelFor, baseDir = "") {
|
|
552
|
-
const data =
|
|
557
|
+
function parseNavYaml(text, labelFor, baseDir = "", load2 = loadYaml) {
|
|
558
|
+
const data = load2(text);
|
|
553
559
|
if (!Array.isArray(data)) return [];
|
|
554
560
|
return data.flatMap((e) => yamlEntry(e, labelFor, baseDir, false));
|
|
555
561
|
}
|
|
@@ -1066,31 +1072,31 @@ var CLIENT_CANDIDATES = ['""', '"&"', '"\\\\$&")).join("|")})', '"\\\\$&")}"', '
|
|
|
1066
1072
|
var SITE_NOTICES = `Third-party software in this site's _dogsbay/ files (dogsbay.js, site.css) and pages
|
|
1067
1073
|
|
|
1068
1074
|
========================================================================
|
|
1069
|
-
@dogsbay/elements@0.2.0-beta.
|
|
1075
|
+
@dogsbay/elements@0.2.0-beta.133 (MIT)
|
|
1070
1076
|
https://github.com/dogsbay/dogsbay.git
|
|
1071
1077
|
|
|
1072
1078
|
Licensed under MIT; the package ships no licence file.
|
|
1073
1079
|
|
|
1074
1080
|
========================================================================
|
|
1075
|
-
@dogsbay/primitives@0.2.0-beta.
|
|
1081
|
+
@dogsbay/primitives@0.2.0-beta.133 (MIT)
|
|
1076
1082
|
https://github.com/dogsbay/dogsbay.git
|
|
1077
1083
|
|
|
1078
1084
|
Licensed under MIT; the package ships no licence file.
|
|
1079
1085
|
|
|
1080
1086
|
========================================================================
|
|
1081
|
-
@dogsbay/render-html@0.2.0-beta.
|
|
1087
|
+
@dogsbay/render-html@0.2.0-beta.133 (MIT)
|
|
1082
1088
|
https://github.com/dogsbay/dogsbay.git
|
|
1083
1089
|
|
|
1084
1090
|
Licensed under MIT; the package ships no licence file.
|
|
1085
1091
|
|
|
1086
1092
|
========================================================================
|
|
1087
|
-
@dogsbay/search@0.2.0-beta.
|
|
1093
|
+
@dogsbay/search@0.2.0-beta.133 (MIT)
|
|
1088
1094
|
https://github.com/dogsbay/dogsbay.git
|
|
1089
1095
|
|
|
1090
1096
|
Licensed under MIT; the package ships no licence file.
|
|
1091
1097
|
|
|
1092
1098
|
========================================================================
|
|
1093
|
-
@dogsbay/ui@0.2.0-beta.
|
|
1099
|
+
@dogsbay/ui@0.2.0-beta.133 (MIT)
|
|
1094
1100
|
https://github.com/dogsbay/dogsbay.git
|
|
1095
1101
|
|
|
1096
1102
|
Licensed under MIT; the package ships no licence file.
|
|
@@ -2164,7 +2170,7 @@ var ThemeTemplates = class {
|
|
|
2164
2170
|
};
|
|
2165
2171
|
|
|
2166
2172
|
// src/core/preprocess.ts
|
|
2167
|
-
import { render } from "@dogsbay/minja/browser";
|
|
2173
|
+
import { render, RenderLimitError } from "@dogsbay/minja/browser";
|
|
2168
2174
|
import {
|
|
2169
2175
|
maskMinjaInCode,
|
|
2170
2176
|
unmaskMinjaInCode,
|
|
@@ -2194,11 +2200,59 @@ var MapLoader = class {
|
|
|
2194
2200
|
throw new Error(`include not found: ${path}${basePath ? ` (from ${basePath})` : ""}`);
|
|
2195
2201
|
}
|
|
2196
2202
|
};
|
|
2203
|
+
var TemplateBudget = class {
|
|
2204
|
+
constructor(renderMs, buildMs, now2 = () => Date.now()) {
|
|
2205
|
+
this.renderMs = renderMs;
|
|
2206
|
+
this.buildMs = buildMs;
|
|
2207
|
+
this.now = now2;
|
|
2208
|
+
}
|
|
2209
|
+
spentMs = 0;
|
|
2210
|
+
active = 0;
|
|
2211
|
+
since = 0;
|
|
2212
|
+
/** Renders not run because the budget was spent, by what they were for. */
|
|
2213
|
+
skipped = { pages: 0, titles: 0 };
|
|
2214
|
+
get spent() {
|
|
2215
|
+
return this.spentMs + (this.active ? this.now() - this.since : 0);
|
|
2216
|
+
}
|
|
2217
|
+
/** The time the next render may take; 0 when the budget is spent. */
|
|
2218
|
+
next() {
|
|
2219
|
+
return Math.max(0, Math.min(this.renderMs, this.buildMs - this.spent));
|
|
2220
|
+
}
|
|
2221
|
+
start() {
|
|
2222
|
+
if (this.active++ === 0) this.since = this.now();
|
|
2223
|
+
}
|
|
2224
|
+
end() {
|
|
2225
|
+
if (--this.active === 0) this.spentMs += this.now() - this.since;
|
|
2226
|
+
}
|
|
2227
|
+
reset() {
|
|
2228
|
+
this.spentMs = 0;
|
|
2229
|
+
this.since = this.now();
|
|
2230
|
+
this.skipped = { pages: 0, titles: 0 };
|
|
2231
|
+
}
|
|
2232
|
+
/** The problem to report when renders were skipped since the last reset, else undefined. */
|
|
2233
|
+
problem() {
|
|
2234
|
+
const { pages, titles } = this.skipped;
|
|
2235
|
+
if (!pages && !titles) return void 0;
|
|
2236
|
+
const what = [pages && `${pages} page(s)`, titles && `${titles} title(s)`].filter(Boolean).join(" and ");
|
|
2237
|
+
return `[template] the template time budget (${this.buildMs / 1e3} s) ran out: ${what} shown without their templates rendered`;
|
|
2238
|
+
}
|
|
2239
|
+
};
|
|
2240
|
+
function templateBudget(untrusted, given) {
|
|
2241
|
+
return new TemplateBudget(given?.renderMs ?? (untrusted ? 2e3 : 1e4), given?.buildMs ?? (untrusted ? 3e4 : 6e5));
|
|
2242
|
+
}
|
|
2197
2243
|
function needsTemplating(text) {
|
|
2198
2244
|
return text.includes("{{") || text.includes("{%");
|
|
2199
2245
|
}
|
|
2200
|
-
async function preprocess(text, path, attributes, files, undefinedRefs = "preserve") {
|
|
2246
|
+
async function preprocess(text, path, attributes, files, undefinedRefs = "preserve", run = {}) {
|
|
2201
2247
|
if (!needsTemplating(text)) return { text };
|
|
2248
|
+
const budget = run.budget;
|
|
2249
|
+
const timeout = budget ? budget.next() : 1e4;
|
|
2250
|
+
const skip = () => {
|
|
2251
|
+
if (run.kind !== "copy") budget.skipped[run.kind === "title" ? "titles" : "pages"]++;
|
|
2252
|
+
return { text, skipped: true };
|
|
2253
|
+
};
|
|
2254
|
+
if (budget && timeout <= 0) return skip();
|
|
2255
|
+
budget?.start();
|
|
2202
2256
|
try {
|
|
2203
2257
|
const out = await render(mask(text), {
|
|
2204
2258
|
context: withUnderscoreAliases(attributes),
|
|
@@ -2207,11 +2261,15 @@ async function preprocess(text, path, attributes, files, undefinedRefs = "preser
|
|
|
2207
2261
|
hyphenToUnderscore: true,
|
|
2208
2262
|
basePath: dirname(path),
|
|
2209
2263
|
loader: new MapLoader(files),
|
|
2210
|
-
timeout
|
|
2264
|
+
timeout,
|
|
2265
|
+
...run.parseCache !== void 0 ? { parseCache: run.parseCache } : {}
|
|
2211
2266
|
});
|
|
2212
2267
|
return { text: stripFenceMinjaMarkers(stripInlineMinjaMarkers(unmask(out))) };
|
|
2213
2268
|
} catch (err) {
|
|
2269
|
+
if (budget && timeout < budget.renderMs && err instanceof RenderLimitError && budget.next() <= 0) return skip();
|
|
2214
2270
|
return { text, error: err instanceof Error ? err.message : String(err) };
|
|
2271
|
+
} finally {
|
|
2272
|
+
budget?.end();
|
|
2215
2273
|
}
|
|
2216
2274
|
}
|
|
2217
2275
|
function withUnderscoreAliases(attrs2) {
|
|
@@ -2223,6 +2281,84 @@ function withUnderscoreAliases(attrs2) {
|
|
|
2223
2281
|
return out;
|
|
2224
2282
|
}
|
|
2225
2283
|
|
|
2284
|
+
// src/core/build.ts
|
|
2285
|
+
import { ParseCache, DEFAULT_PARSE_CACHE_BYTES } from "@dogsbay/minja/browser";
|
|
2286
|
+
|
|
2287
|
+
// src/core/scan-cache.ts
|
|
2288
|
+
var ScanCache = class {
|
|
2289
|
+
entries = /* @__PURE__ */ new Map();
|
|
2290
|
+
/** Project path of a file read while resolving titles → the pages whose entries read it. */
|
|
2291
|
+
readers = /* @__PURE__ */ new Map();
|
|
2292
|
+
/** The last nav YAML loaded, by its text: an unchanged nav.yml is not parsed again. */
|
|
2293
|
+
navText;
|
|
2294
|
+
navData;
|
|
2295
|
+
/** Resolved topic maps, with what they were resolved with and the file map's value of every file read. */
|
|
2296
|
+
maps = /* @__PURE__ */ new Map();
|
|
2297
|
+
/** The page's entry when it was made from this content, else a new empty one for it. */
|
|
2298
|
+
entry(path, content) {
|
|
2299
|
+
const had = this.entries.get(path);
|
|
2300
|
+
if (had && had.content === content) return had;
|
|
2301
|
+
if (had) this.dropDeps(path, had);
|
|
2302
|
+
const e = { content };
|
|
2303
|
+
this.entries.set(path, e);
|
|
2304
|
+
return e;
|
|
2305
|
+
}
|
|
2306
|
+
/** Records that a page's title or description read a file, so a change to that file drops them. */
|
|
2307
|
+
read(page, file) {
|
|
2308
|
+
const e = this.entries.get(page);
|
|
2309
|
+
if (!e) return;
|
|
2310
|
+
(e.deps ??= /* @__PURE__ */ new Set()).add(file);
|
|
2311
|
+
let r = this.readers.get(file);
|
|
2312
|
+
if (!r) this.readers.set(file, r = /* @__PURE__ */ new Set());
|
|
2313
|
+
r.add(page);
|
|
2314
|
+
}
|
|
2315
|
+
/** A file changed, was added or was removed: its own entry goes, and the titles that read it. */
|
|
2316
|
+
changed(path) {
|
|
2317
|
+
const own = this.entries.get(path);
|
|
2318
|
+
if (own) {
|
|
2319
|
+
this.dropDeps(path, own);
|
|
2320
|
+
this.entries.delete(path);
|
|
2321
|
+
}
|
|
2322
|
+
const readers = this.readers.get(path);
|
|
2323
|
+
if (!readers) return;
|
|
2324
|
+
this.readers.delete(path);
|
|
2325
|
+
for (const page of readers) {
|
|
2326
|
+
const e = this.entries.get(page);
|
|
2327
|
+
if (!e) continue;
|
|
2328
|
+
this.dropDeps(page, e);
|
|
2329
|
+
delete e.title;
|
|
2330
|
+
delete e.description;
|
|
2331
|
+
delete e.resolvedWith;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
/** `load(text)`, or the value it gave for the same text last time (the caller must not change it). */
|
|
2335
|
+
yaml(text, load2) {
|
|
2336
|
+
if (this.navText !== text) {
|
|
2337
|
+
this.navData = load2(text);
|
|
2338
|
+
this.navText = text;
|
|
2339
|
+
}
|
|
2340
|
+
return this.navData;
|
|
2341
|
+
}
|
|
2342
|
+
/** A topic map resolved with `key` (its variables), if no file it read has changed since. */
|
|
2343
|
+
topicMap(path, key, files) {
|
|
2344
|
+
const m = this.maps.get(path);
|
|
2345
|
+
if (!m || m.key !== key) return void 0;
|
|
2346
|
+
for (const [p, c] of m.reads) if (files.get(p) !== c) return void 0;
|
|
2347
|
+
return m.nav;
|
|
2348
|
+
}
|
|
2349
|
+
/** Keeps a resolved topic map (not one with errors: the build stops on those) and the files it read. */
|
|
2350
|
+
keepTopicMap(path, key, reads, nav) {
|
|
2351
|
+
if (!nav.errors.length) this.maps.set(path, { key, reads, nav });
|
|
2352
|
+
}
|
|
2353
|
+
get size() {
|
|
2354
|
+
return this.entries.size;
|
|
2355
|
+
}
|
|
2356
|
+
dropDeps(page, e) {
|
|
2357
|
+
for (const f of e.deps ?? []) this.readers.get(f)?.delete(page);
|
|
2358
|
+
e.deps = void 0;
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
|
|
2226
2362
|
// src/core/build.ts
|
|
2227
2363
|
import { TemplatePages } from "@dogsbay/format-dogsbay-md/browser";
|
|
2228
2364
|
import { extractText, SearchIndexBuilder } from "@dogsbay/search/index";
|
|
@@ -2896,6 +3032,16 @@ function fmKey(src) {
|
|
|
2896
3032
|
return detach(`${m ? m[0] : ""}
|
|
2897
3033
|
${firstH1(src) ?? ""}`);
|
|
2898
3034
|
}
|
|
3035
|
+
function detachValue(v) {
|
|
3036
|
+
if (typeof v === "string") return detach(v);
|
|
3037
|
+
if (Array.isArray(v)) return v.map(detachValue);
|
|
3038
|
+
if (v && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype) {
|
|
3039
|
+
const o = {};
|
|
3040
|
+
for (const [k, x] of Object.entries(v)) o[detach(k)] = detachValue(x);
|
|
3041
|
+
return o;
|
|
3042
|
+
}
|
|
3043
|
+
return v;
|
|
3044
|
+
}
|
|
2899
3045
|
function frontMatterCopy(fm) {
|
|
2900
3046
|
try {
|
|
2901
3047
|
const copy = JSON.parse(JSON.stringify(fm));
|
|
@@ -2942,7 +3088,7 @@ async function build(input, hooks = {}) {
|
|
|
2942
3088
|
phase.init = now() - t0;
|
|
2943
3089
|
let pages = 0;
|
|
2944
3090
|
let assets = 0;
|
|
2945
|
-
const state = { redirects: [], navCache: navCache(), written: /* @__PURE__ */ new Set(), stubs: [], pageOut: /* @__PURE__ */ new Map(), sitemaps: /* @__PURE__ */ new Map(), supplied: /* @__PURE__ */ new Set(), usedMath: false, diagrams: diagramStore(input, cfg) };
|
|
3091
|
+
const state = { redirects: [], navCache: navCache(), written: /* @__PURE__ */ new Set(), stubs: [], pageOut: /* @__PURE__ */ new Map(), sitemaps: /* @__PURE__ */ new Map(), supplied: /* @__PURE__ */ new Set(), usedMath: false, diagrams: diagramStore(input, cfg), scan: hooks.keep || hooks.lazy ? new ScanCache() : void 0, parseCache: input.parseCacheBytes === 0 ? false : new ParseCache(input.parseCacheBytes ?? DEFAULT_PARSE_CACHE_BYTES), templateBudget: templateBudget(input.untrusted === true, input.templateMs) };
|
|
2946
3092
|
for (const r of cfg.redirects) {
|
|
2947
3093
|
const from = sitePath(r.from, cfg.basePath);
|
|
2948
3094
|
if (!from) {
|
|
@@ -3013,12 +3159,14 @@ async function build(input, hooks = {}) {
|
|
|
3013
3159
|
}
|
|
3014
3160
|
}
|
|
3015
3161
|
state.stubs = await writeTranslationStubs(cfg, allUnits, units, translations, state, emit);
|
|
3162
|
+
const spent = state.templateBudget.problem();
|
|
3163
|
+
if (spent) report("template", spent);
|
|
3016
3164
|
return {
|
|
3017
3165
|
files: out,
|
|
3018
3166
|
warnings,
|
|
3019
3167
|
problems,
|
|
3020
3168
|
config: cfg,
|
|
3021
|
-
stats: { pages, assets, units: units.length, ms: Math.round(now() - t0), msByPhase: roundAll(phase), highlightCache: highlighter.cacheStats() }
|
|
3169
|
+
stats: { pages, assets, units: units.length, ms: Math.round(now() - t0), msByPhase: roundAll(phase), highlightCache: highlighter.cacheStats(), ...state.parseCache ? { parseCache: state.parseCache.stats() } : {} }
|
|
3022
3170
|
};
|
|
3023
3171
|
}
|
|
3024
3172
|
function selectUnits(allUnits, names) {
|
|
@@ -3116,9 +3264,14 @@ async function buildUnit(unit, allUnits, cfg, input, highlighter, emit, report,
|
|
|
3116
3264
|
const ns = named.length ? namespaceOf(src) : "";
|
|
3117
3265
|
return ns ? (p) => p === ns || p.startsWith(ns + "/") ? p : `${ns}/${p}` : void 0;
|
|
3118
3266
|
};
|
|
3119
|
-
const includesFor = (src) => {
|
|
3267
|
+
const includesFor = (src, read2) => {
|
|
3120
3268
|
const at = scoped(src);
|
|
3121
|
-
return at ? (p) => readInclude(at(p)) : readInclude;
|
|
3269
|
+
if (!read2) return at ? (p) => readInclude(at(p)) : readInclude;
|
|
3270
|
+
return (p) => {
|
|
3271
|
+
const q = at ? at(p) : p;
|
|
3272
|
+
read2(abs(q));
|
|
3273
|
+
return readInclude(q);
|
|
3274
|
+
};
|
|
3122
3275
|
};
|
|
3123
3276
|
const namespaceOf = (p) => named.find((m) => p === m.namespace || p.startsWith(m.namespace + "/"))?.namespace ?? "";
|
|
3124
3277
|
const globalPrefixes = new Set(cfg.taxonomies.map((t2) => t2.path.split("/")[0]).filter(Boolean));
|
|
@@ -3156,6 +3309,7 @@ async function buildUnit(unit, allUnits, cfg, input, highlighter, emit, report,
|
|
|
3156
3309
|
return c == null ? void 0 : readText(c);
|
|
3157
3310
|
};
|
|
3158
3311
|
const includedPaths = /* @__PURE__ */ new Set();
|
|
3312
|
+
const includedProject = /* @__PURE__ */ new Set();
|
|
3159
3313
|
const readInclude = async (p) => {
|
|
3160
3314
|
if (mode.keep || mode.lazy) includedPaths.add(p);
|
|
3161
3315
|
const hit = cache.get(p);
|
|
@@ -3168,7 +3322,8 @@ async function buildUnit(unit, allUnits, cfg, input, highlighter, emit, report,
|
|
|
3168
3322
|
};
|
|
3169
3323
|
const resolvers = [openApiResolver(read, input.openApiChars), autodocUnresolved, ...input.resolvers?.(unit) ?? []];
|
|
3170
3324
|
const hasContent = (p) => !buildOnly(p) && input.files.has(abs(p));
|
|
3171
|
-
const
|
|
3325
|
+
const templateRun = { parseCache: state.parseCache, budget: state.templateBudget, kind: "page" };
|
|
3326
|
+
const resolveTitle = async (title, src, path, pageFm, read2) => {
|
|
3172
3327
|
if (!isTemplated(title)) return title;
|
|
3173
3328
|
if (pageFm && templatesOf(path).isTemplatePage(pageFm)) {
|
|
3174
3329
|
return (await templatesOf(path).renderField(title, path, pageFm)).trim();
|
|
@@ -3178,7 +3333,7 @@ async function buildUnit(unit, allUnits, cfg, input, highlighter, emit, report,
|
|
|
3178
3333
|
const preamble = head.split("\n").filter((l) => /^\s*\{%-?\s*(include|set)\b/.test(l)).join("\n");
|
|
3179
3334
|
const marker = "DBTITLEMARKER";
|
|
3180
3335
|
const r = await preprocess(`${preamble}
|
|
3181
|
-
${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.undefinedRefs);
|
|
3336
|
+
${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2), cfg.undefinedRefs, { parseCache: state.parseCache, budget: state.templateBudget, kind: "title" });
|
|
3182
3337
|
const m = r.text.split(marker);
|
|
3183
3338
|
return (m.length >= 3 ? m[1] : title).trim();
|
|
3184
3339
|
};
|
|
@@ -3186,13 +3341,23 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3186
3341
|
for (const m of unit.members) {
|
|
3187
3342
|
const path = m.source.nav;
|
|
3188
3343
|
if (!path || !TOPIC_MAP.test(path) || topicMaps.has(path)) continue;
|
|
3344
|
+
const variables = withUnderscoreAliases({ ...cfg.attributes, ...m.source.attributes ?? {} });
|
|
3345
|
+
const key = state.scan ? JSON.stringify([sourceRoot(m.source), variables]) : "";
|
|
3346
|
+
const kept = state.scan?.topicMap(path, key, input.files);
|
|
3347
|
+
if (kept) {
|
|
3348
|
+
topicMaps.set(path, kept);
|
|
3349
|
+
continue;
|
|
3350
|
+
}
|
|
3189
3351
|
const { topicMapError, topicMapNav } = await import("./chunks/topicmap-PY3WDCSI.mjs");
|
|
3352
|
+
const reads = /* @__PURE__ */ new Map();
|
|
3190
3353
|
const projectText = async (p) => {
|
|
3191
3354
|
const c = input.files.get(p);
|
|
3355
|
+
reads.set(p, c);
|
|
3192
3356
|
return c == null ? void 0 : readText(c);
|
|
3193
3357
|
};
|
|
3194
|
-
const got = await topicMapNav(path, sourceRoot(m.source), projectText,
|
|
3358
|
+
const got = await topicMapNav(path, sourceRoot(m.source), projectText, variables);
|
|
3195
3359
|
if (got.errors.length) throw topicMapError(path, got.errors);
|
|
3360
|
+
state.scan?.keepTopicMap(path, key, reads, got);
|
|
3196
3361
|
topicMaps.set(path, got);
|
|
3197
3362
|
}
|
|
3198
3363
|
const navInputs = /* @__PURE__ */ new Set([...unit.members.flatMap((m) => m.source.nav ? [m.source.nav] : []), ...[...topicMaps.values()].flatMap((t2) => t2.maps)]);
|
|
@@ -3213,6 +3378,13 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3213
3378
|
}
|
|
3214
3379
|
return a;
|
|
3215
3380
|
};
|
|
3381
|
+
const resolvedKeys = /* @__PURE__ */ new Map();
|
|
3382
|
+
const resolvedKey = (p) => {
|
|
3383
|
+
const key = named.length ? namespaceOf(p) : "";
|
|
3384
|
+
let k = resolvedKeys.get(key);
|
|
3385
|
+
if (k === void 0) resolvedKeys.set(key, k = JSON.stringify([label, key, sourceOf(p).templates ?? null, cfg.name, cfg.undefinedRefs, attributesOf(p)]));
|
|
3386
|
+
return k;
|
|
3387
|
+
};
|
|
3216
3388
|
const templatesBy = /* @__PURE__ */ new Map();
|
|
3217
3389
|
const templatesOf = (p) => {
|
|
3218
3390
|
const key = named.length ? namespaceOf(p) : "";
|
|
@@ -3239,11 +3411,17 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3239
3411
|
if (/\.(md|markdown)$/i.test(p)) {
|
|
3240
3412
|
if (excluded(p)) continue;
|
|
3241
3413
|
const slug = slugFromPath(p);
|
|
3242
|
-
const
|
|
3414
|
+
const memo2 = state.scan?.entry(abs(p), c);
|
|
3415
|
+
let text;
|
|
3416
|
+
const source = async () => text ??= await readText(c);
|
|
3243
3417
|
let fm = {};
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3418
|
+
if (memo2?.fm) fm = memo2.fm;
|
|
3419
|
+
else {
|
|
3420
|
+
try {
|
|
3421
|
+
fm = splitFrontmatter2(await source()).data ?? {};
|
|
3422
|
+
} catch {
|
|
3423
|
+
}
|
|
3424
|
+
if (memo2) memo2.fm = detachValue(fm);
|
|
3247
3425
|
}
|
|
3248
3426
|
if (fm.status === "draft" && !input.includeDrafts) continue;
|
|
3249
3427
|
if (templatesOf(p).isPartial(fm, p)) continue;
|
|
@@ -3279,20 +3457,35 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3279
3457
|
warn("duplicate-page", `two sources map to the page "${slug || "/"}"; keeping ${srcOf(infos.get(slug))}, ignoring ${abs(p)}`, { file: abs(p) });
|
|
3280
3458
|
continue;
|
|
3281
3459
|
}
|
|
3460
|
+
const resolvedWith = memo2 ? resolvedKey(p) : void 0;
|
|
3461
|
+
let title;
|
|
3462
|
+
let description;
|
|
3463
|
+
if (memo2 && memo2.title !== void 0 && memo2.resolvedWith === resolvedWith) {
|
|
3464
|
+
title = memo2.title;
|
|
3465
|
+
description = memo2.description;
|
|
3466
|
+
for (const f of memo2.deps ?? []) includedProject.add(f);
|
|
3467
|
+
} else {
|
|
3468
|
+
const keep = memo2 && !templatesOf(p).isTemplatePage(fm);
|
|
3469
|
+
const reads = keep ? (f) => state.scan.read(abs(p), f) : void 0;
|
|
3470
|
+
const skippedBefore = state.templateBudget.skipped.titles;
|
|
3471
|
+
title = detach(await resolveTitle(str(fm.title) ?? firstH1(await source()) ?? humanize2(slug.split("/").pop() || cfg.name), await source(), p, fm, reads));
|
|
3472
|
+
description = fm.description != null ? detach(await resolveTitle(String(fm.description), await source(), p, fm, reads)) : void 0;
|
|
3473
|
+
if (keep && state.templateBudget.skipped.titles === skippedBefore) Object.assign(memo2, { title, description, resolvedWith });
|
|
3474
|
+
}
|
|
3282
3475
|
infos.set(slug, {
|
|
3283
3476
|
slug,
|
|
3284
3477
|
source: p,
|
|
3285
3478
|
out: outFileFor(slug, isIndexFile),
|
|
3286
|
-
title
|
|
3479
|
+
title,
|
|
3287
3480
|
order: typeof fm.order === "number" ? fm.order : typeof fm.weight === "number" ? fm.weight : void 0,
|
|
3288
3481
|
draft: fm.draft === true || fm.status === "draft",
|
|
3289
|
-
description
|
|
3482
|
+
description,
|
|
3290
3483
|
data: frontMatterCopy(fm)
|
|
3291
3484
|
});
|
|
3292
|
-
if (diagrams) for (const code of fencedDiagrams(
|
|
3485
|
+
if (diagrams) for (const code of memo2 ? memo2.diagrams ??= fencedDiagrams(await source()).map(detach) : fencedDiagrams(await source())) diagrams.note(detach(code), p);
|
|
3293
3486
|
taxonomies?.add(slug, fm);
|
|
3294
3487
|
blog?.add(slug, infos.get(slug).title, fm);
|
|
3295
|
-
if (mode.keep || mode.lazy) frontMatter.set(p, fmKey(
|
|
3488
|
+
if (mode.keep || mode.lazy) frontMatter.set(p, memo2 ? memo2.fmKey ??= fmKey(await source()) : fmKey(await source()));
|
|
3296
3489
|
} else if (!SKIP_FILES.has(memberRel(p)) && copiedPath(p)) {
|
|
3297
3490
|
const verdict = await assetVerdict(p, c, input.untrusted === true, sanitizing);
|
|
3298
3491
|
if (verdict === "untrusted") untrustedFiles.push(p);
|
|
@@ -3353,6 +3546,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3353
3546
|
if (strays.length) warn("openapi", `${strays.length} page(s) are in the folder of the OpenAPI source "${ns}", and are left out of the nav: ${strays.slice(0, 5).map(srcOf).join(", ")}${strays.length > 5 ? ", \u2026" : ""}`);
|
|
3354
3547
|
}
|
|
3355
3548
|
const labelFor = (slug) => infos.get(slug)?.title;
|
|
3549
|
+
const navLoad = state.scan ? (text) => state.scan.yaml(text, loadYaml) : void 0;
|
|
3356
3550
|
const memberNav = async (ns) => {
|
|
3357
3551
|
const dir2 = ns ? ns + "/" : "";
|
|
3358
3552
|
const member = unit.members.find((m) => m.namespace === ns);
|
|
@@ -3364,7 +3558,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3364
3558
|
if (!file) return { nav: fromPages() };
|
|
3365
3559
|
try {
|
|
3366
3560
|
const text = await read(file);
|
|
3367
|
-
return { nav: file.endsWith(".json") ? prefixNav(parseNavJson(text, (slug) => labelFor(ns ? prefixSlug([ns], slug) : slug)), ns) : parseNavYaml(text, labelFor, ns), file, project: abs(file) };
|
|
3561
|
+
return { nav: file.endsWith(".json") ? prefixNav(parseNavJson(text, (slug) => labelFor(ns ? prefixSlug([ns], slug) : slug)), ns) : parseNavYaml(text, labelFor, ns, navLoad), file, project: abs(file) };
|
|
3368
3562
|
} catch (e) {
|
|
3369
3563
|
const mark = e.mark;
|
|
3370
3564
|
warn("nav", `could not read ${file}: ${e.message}; using the directory layout`, {
|
|
@@ -3382,7 +3576,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3382
3576
|
const c = input.files.get(path);
|
|
3383
3577
|
const text = c == null ? void 0 : await readText(c);
|
|
3384
3578
|
if (text === void 0) throw new ConfigError(`content.sources (${src.path}): its nav file ${path} does not exist`);
|
|
3385
|
-
return { nav: path.endsWith(".json") ? prefixNav(parseNavJson(text, nsLabel), ns) : parseNavYaml(text, labelFor, ns), file: path, project: path, files: [path] };
|
|
3579
|
+
return { nav: path.endsWith(".json") ? prefixNav(parseNavJson(text, nsLabel), ns) : parseNavYaml(text, labelFor, ns, navLoad), file: path, project: path, files: [path] };
|
|
3386
3580
|
};
|
|
3387
3581
|
let nav = [];
|
|
3388
3582
|
const navFiles = [];
|
|
@@ -3510,7 +3704,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3510
3704
|
warn2("front-matter", `front matter does not parse${line ? ` (line ${line})` : ""}: ${reason}`, line);
|
|
3511
3705
|
if (mode.diagnostics) diagnostics = [{ code: "front-matter", message: detach(`front matter does not parse: ${reason}`), ...line ? { line } : {} }];
|
|
3512
3706
|
}
|
|
3513
|
-
const pre = templatesOf(info.source).isTemplatePage(fm) ? { text: await templatesOf(info.source).render(bodySrc, info.source, fm, { firstLine: raw.slice(0, raw.length - bodySrc.length).split("\n").length }) } : await preprocess(bodySrc, info.source, attributesOf(info.source), includesFor(info.source), cfg.undefinedRefs);
|
|
3707
|
+
const pre = templatesOf(info.source).isTemplatePage(fm) ? { text: await templatesOf(info.source).render(bodySrc, info.source, fm, { firstLine: raw.slice(0, raw.length - bodySrc.length).split("\n").length }) } : await preprocess(bodySrc, info.source, attributesOf(info.source), includesFor(info.source), cfg.undefinedRefs, templateRun);
|
|
3514
3708
|
if (pre.error) {
|
|
3515
3709
|
warn2("template", `template error: ${pre.error}`);
|
|
3516
3710
|
if (mode.diagnostics) (diagnostics ??= []).push({ code: "template", message: detach(`template error: ${pre.error}`) });
|
|
@@ -3857,7 +4051,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3857
4051
|
body = sp.content;
|
|
3858
4052
|
} catch {
|
|
3859
4053
|
}
|
|
3860
|
-
const text = templatesOf(info.source).isTemplatePage(pageFm) ? await templatesOf(info.source).render(body, info.source, pageFm) : (await preprocess(body, info.source, attributesOf(info.source), includesFor(info.source), cfg.undefinedRefs)).text;
|
|
4054
|
+
const text = templatesOf(info.source).isTemplatePage(pageFm) ? await templatesOf(info.source).render(body, info.source, pageFm) : (await preprocess(body, info.source, attributesOf(info.source), includesFor(info.source), cfg.undefinedRefs, { ...templateRun, kind: "copy" })).text;
|
|
3861
4055
|
const ns = named.length ? namespaceOf(info.source) : "";
|
|
3862
4056
|
if (!ns && !PLACEHOLDER_DIRECTIVE.test(text)) return text;
|
|
3863
4057
|
const tree = dogsbayMdToTree(text).tree;
|
|
@@ -3920,7 +4114,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path), cfg.un
|
|
|
3920
4114
|
},
|
|
3921
4115
|
included(path) {
|
|
3922
4116
|
const r = rel(path);
|
|
3923
|
-
return r !== void 0 && includedPaths.has(r);
|
|
4117
|
+
return r !== void 0 && includedPaths.has(r) || includedProject.has(path);
|
|
3924
4118
|
}
|
|
3925
4119
|
};
|
|
3926
4120
|
async function emitGenerated() {
|
|
@@ -4450,6 +4644,7 @@ async function openDevSession(input, o = {}) {
|
|
|
4450
4644
|
const rendering = /* @__PURE__ */ new Map();
|
|
4451
4645
|
let ctx;
|
|
4452
4646
|
let config;
|
|
4647
|
+
let parsedConfig;
|
|
4453
4648
|
let taken = 0;
|
|
4454
4649
|
let takenProblems = 0;
|
|
4455
4650
|
let buildError;
|
|
@@ -4493,6 +4688,7 @@ async function openDevSession(input, o = {}) {
|
|
|
4493
4688
|
} catch (e) {
|
|
4494
4689
|
throw e instanceof ConfigError ? inConfig(e, name) : e;
|
|
4495
4690
|
}
|
|
4691
|
+
parsedConfig = parsed;
|
|
4496
4692
|
out = next;
|
|
4497
4693
|
units = nextUnits;
|
|
4498
4694
|
ctx = nextCtx;
|
|
@@ -4534,8 +4730,23 @@ async function openDevSession(input, o = {}) {
|
|
|
4534
4730
|
const css = await (await siteCssBuilder(input))({ theme: config.theme, themeCss: ctx.theme.themeCss, extra: ctx.theme.extraCss, candidates: u.classes });
|
|
4535
4731
|
out.set(`${u.unit.prefix ? u.unit.prefix + "/" : ""}_dogsbay/site.css`, css);
|
|
4536
4732
|
};
|
|
4733
|
+
let requests = Promise.resolve();
|
|
4734
|
+
const budgeted = (work) => {
|
|
4735
|
+
const run = requests.then(async () => {
|
|
4736
|
+
const budget = ctx?.state.templateBudget;
|
|
4737
|
+
budget?.reset();
|
|
4738
|
+
try {
|
|
4739
|
+
return await work();
|
|
4740
|
+
} finally {
|
|
4741
|
+
const spent = ctx?.state.templateBudget === budget ? budget?.problem() : void 0;
|
|
4742
|
+
if (spent) ctx.report("template", spent);
|
|
4743
|
+
}
|
|
4744
|
+
});
|
|
4745
|
+
requests = run.catch(() => void 0);
|
|
4746
|
+
return run;
|
|
4747
|
+
};
|
|
4537
4748
|
const renderStale = async (path, u) => {
|
|
4538
|
-
await u.handle.render(path);
|
|
4749
|
+
await budgeted(() => u.handle.render(path));
|
|
4539
4750
|
stale.delete(path);
|
|
4540
4751
|
const html = out.get(path);
|
|
4541
4752
|
if (typeof html === "string") {
|
|
@@ -4611,48 +4822,80 @@ async function openDevSession(input, o = {}) {
|
|
|
4611
4822
|
},
|
|
4612
4823
|
get,
|
|
4613
4824
|
async change(paths) {
|
|
4825
|
+
const t0 = Date.now();
|
|
4826
|
+
const done = (r) => ({ ...r, ms: Date.now() - t0 });
|
|
4614
4827
|
try {
|
|
4615
4828
|
if (buildError !== void 0) {
|
|
4616
4829
|
await full();
|
|
4617
4830
|
buildError = void 0;
|
|
4618
4831
|
buildProblem = void 0;
|
|
4619
|
-
return { css: true };
|
|
4832
|
+
return done({ css: true, handled: "full" });
|
|
4620
4833
|
}
|
|
4621
|
-
return await changeParts(paths);
|
|
4834
|
+
return done(await budgeted(() => changeParts(paths)));
|
|
4622
4835
|
} catch (e) {
|
|
4623
4836
|
failed(e);
|
|
4624
|
-
return { css: false };
|
|
4837
|
+
return done({ css: false, handled: "full" });
|
|
4625
4838
|
}
|
|
4626
4839
|
}
|
|
4627
4840
|
};
|
|
4628
4841
|
async function changeParts(paths) {
|
|
4629
4842
|
const themeDir = config.themeDir ? config.themeDir.replace(/\/+$/, "") + "/" : void 0;
|
|
4630
4843
|
const extraCss = new Set(config.css.map((c) => c.replace(/^\.\//, "")));
|
|
4631
|
-
const
|
|
4632
|
-
|
|
4633
|
-
|
|
4844
|
+
const lazy = /* @__PURE__ */ new Set();
|
|
4845
|
+
if (paths.some((p) => CONFIG_FILES.has(p))) {
|
|
4846
|
+
const next = await attributesOnly();
|
|
4847
|
+
if (!next) {
|
|
4848
|
+
await full();
|
|
4849
|
+
return { css: true, handled: "full" };
|
|
4850
|
+
}
|
|
4851
|
+
ctx.cfg = { ...ctx.cfg, attributes: next.attributes };
|
|
4852
|
+
config = { ...config, attributes: next.attributes };
|
|
4853
|
+
const same = JSON.stringify(next.attributes) === JSON.stringify(parsedConfig?.attributes);
|
|
4854
|
+
parsedConfig = next;
|
|
4855
|
+
if (!same) for (const prefix of units.keys()) lazy.add(prefix);
|
|
4856
|
+
}
|
|
4857
|
+
if (paths.some((p) => themeDir && p.startsWith(themeDir) || extraCss.has(p))) {
|
|
4634
4858
|
await full();
|
|
4635
|
-
return { css: true };
|
|
4859
|
+
return { css: true, handled: "full" };
|
|
4636
4860
|
}
|
|
4637
|
-
const
|
|
4861
|
+
for (const p of paths) ctx.state.scan?.changed(p);
|
|
4862
|
+
let handled = lazy.size ? "unit" : "none";
|
|
4863
|
+
const mark = (h) => {
|
|
4864
|
+
const rank = ["none", "asset", "page", "unit", "full"];
|
|
4865
|
+
if (rank.indexOf(h) > rank.indexOf(handled)) handled = h;
|
|
4866
|
+
};
|
|
4638
4867
|
for (const p of paths) {
|
|
4868
|
+
if (CONFIG_FILES.has(p)) continue;
|
|
4869
|
+
const readers = [...units.values()].filter((u2) => u2.handle.navInputs.includes(p) || u2.unit.members.some((m) => m.source.nav === p));
|
|
4870
|
+
if (readers.length) {
|
|
4871
|
+
for (const u2 of [...readers, unitOf(p)]) if (u2) lazy.add(u2.unit.prefix);
|
|
4872
|
+
mark("unit");
|
|
4873
|
+
continue;
|
|
4874
|
+
}
|
|
4875
|
+
if (TOPIC_MAP.test(p)) continue;
|
|
4639
4876
|
const u = unitOf(p);
|
|
4640
4877
|
if (!u) continue;
|
|
4641
4878
|
const content = input.files.get(p);
|
|
4642
4879
|
if (/\.(md|markdown)$/i.test(p)) {
|
|
4643
4880
|
const page = u.handle.pageForSource(p);
|
|
4644
4881
|
const text = content === void 0 ? void 0 : await textOf(content);
|
|
4645
|
-
if (page && text !== void 0 && u.handle.sameFrontMatter(p, text) && !u.handle.included(p))
|
|
4646
|
-
|
|
4882
|
+
if (page && text !== void 0 && u.handle.sameFrontMatter(p, text) && !u.handle.included(p)) {
|
|
4883
|
+
stale.add(page);
|
|
4884
|
+
mark("page");
|
|
4885
|
+
} else {
|
|
4647
4886
|
u.handle.forget(p);
|
|
4648
4887
|
lazy.add(u.unit.prefix);
|
|
4888
|
+
mark("unit");
|
|
4649
4889
|
}
|
|
4650
4890
|
} else if (NAV_FILES2.test(p)) {
|
|
4651
4891
|
lazy.add(u.unit.prefix);
|
|
4892
|
+
mark("unit");
|
|
4652
4893
|
} else if (content === void 0 || /\.(md|markdown|ya?ml|json|txt|html?|csv)$/i.test(p)) {
|
|
4653
4894
|
u.handle.forget(p);
|
|
4654
4895
|
lazy.add(u.unit.prefix);
|
|
4896
|
+
mark("unit");
|
|
4655
4897
|
} else {
|
|
4898
|
+
mark("asset");
|
|
4656
4899
|
const at = memberPath(u.unit, p)?.path ?? p.slice(sourceRoot(u.unit.source).length);
|
|
4657
4900
|
const key = u.unit.prefix ? `${u.unit.prefix}/${at}` : at;
|
|
4658
4901
|
const sanitizing = (config.html ?? input.defaultHtml ?? "keep") === "sanitize";
|
|
@@ -4698,7 +4941,20 @@ async function openDevSession(input, o = {}) {
|
|
|
4698
4941
|
(p) => stale.has(p)
|
|
4699
4942
|
);
|
|
4700
4943
|
}
|
|
4701
|
-
return { css: false };
|
|
4944
|
+
return { css: false, handled };
|
|
4945
|
+
}
|
|
4946
|
+
async function attributesOnly() {
|
|
4947
|
+
const file = input.files.get("dogsbay.config.yml") ?? input.files.get("dogsbay.config.yaml");
|
|
4948
|
+
const text = file !== void 0 ? await textOf(file) : void 0;
|
|
4949
|
+
if (text === void 0 || !parsedConfig) return void 0;
|
|
4950
|
+
let next;
|
|
4951
|
+
try {
|
|
4952
|
+
next = parseConfig(text);
|
|
4953
|
+
} catch {
|
|
4954
|
+
return void 0;
|
|
4955
|
+
}
|
|
4956
|
+
const rest = (c) => JSON.stringify({ ...c, attributes: void 0 });
|
|
4957
|
+
return rest(next) === rest(parsedConfig) ? next : void 0;
|
|
4702
4958
|
}
|
|
4703
4959
|
async function emitInto(path, content) {
|
|
4704
4960
|
if (START_ONLY.test(path)) return;
|
|
@@ -4818,5 +5074,6 @@ export {
|
|
|
4818
5074
|
parseNavJson,
|
|
4819
5075
|
parseNavYaml,
|
|
4820
5076
|
peleRenderer,
|
|
4821
|
-
prebuiltSiteCss
|
|
5077
|
+
prebuiltSiteCss,
|
|
5078
|
+
projectRoots
|
|
4822
5079
|
};
|
|
@@ -20,6 +20,9 @@ import { type NavCache } from "../theme/regions.js";
|
|
|
20
20
|
import { ThemeTemplates } from "../theme/page.js";
|
|
21
21
|
import type { CssOptions } from "../theme/css-build.js";
|
|
22
22
|
import { type FileRef, type TextRef, type TextStream } from "./detach.js";
|
|
23
|
+
import { type TemplateBudget } from "./preprocess.js";
|
|
24
|
+
import { ParseCache } from "@dogsbay/minja/browser";
|
|
25
|
+
import { ScanCache } from "./scan-cache.js";
|
|
23
26
|
import { type RedirectInput, type RedirectRule } from "./site-files.js";
|
|
24
27
|
import { DiagramStore, type DiagramRenderer, type DiagramRenderers } from "./diagrams.js";
|
|
25
28
|
export type FileContent = string | Uint8Array | FileRef | TextRef;
|
|
@@ -90,6 +93,19 @@ export interface BuildInput {
|
|
|
90
93
|
* 8–16 MB). The CLI passes 64M (up to about 128 MB), the Worker 4M.
|
|
91
94
|
*/
|
|
92
95
|
highlightCacheChars?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Estimated bytes of included templates' parses kept for reuse (minja's `ParseCache`; default
|
|
98
|
+
* 8 MB, which holds every reuse on OpenShift). 0 parses every include again.
|
|
99
|
+
*/
|
|
100
|
+
parseCacheBytes?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Template time: per render, and for the whole build (a dev session: for opening it, and for
|
|
103
|
+
* each change or page request). Defaults: 10 s and 10 minutes; an untrusted build 2 s and 30 s.
|
|
104
|
+
*/
|
|
105
|
+
templateMs?: {
|
|
106
|
+
renderMs?: number;
|
|
107
|
+
buildMs?: number;
|
|
108
|
+
};
|
|
93
109
|
/** Characters of rendered SVG kept for reuse across units (default 8M; a Worker passes less). */
|
|
94
110
|
diagramCacheChars?: number;
|
|
95
111
|
/**
|
|
@@ -189,6 +205,12 @@ export interface SiteState {
|
|
|
189
205
|
written: Set<string>;
|
|
190
206
|
/** The stubs for missing translations last written, so a dev session can replace them. */
|
|
191
207
|
stubs: string[];
|
|
208
|
+
/** A dev session's: what pass 1 read from each page, kept between its rebuilds (src/core/scan-cache.ts). */
|
|
209
|
+
scan?: ScanCache;
|
|
210
|
+
/** Included templates' parses (false: none kept), kept as long as the state: a dev session's unit rebuilds keep it. */
|
|
211
|
+
parseCache: ParseCache | false;
|
|
212
|
+
/** Template time left for the build, or for a dev session's current request. */
|
|
213
|
+
templateBudget: TemplateBudget;
|
|
192
214
|
}
|
|
193
215
|
export interface BuildStats {
|
|
194
216
|
pages: number;
|
|
@@ -203,6 +225,14 @@ export interface BuildStats {
|
|
|
203
225
|
entries: number;
|
|
204
226
|
chars: number;
|
|
205
227
|
};
|
|
228
|
+
/** The parse cache for includes at the end of the build (minja's `ParseCache.stats()`). */
|
|
229
|
+
parseCache?: {
|
|
230
|
+
hits: number;
|
|
231
|
+
misses: number;
|
|
232
|
+
entries: number;
|
|
233
|
+
bytes: number;
|
|
234
|
+
maxBytes: number;
|
|
235
|
+
};
|
|
206
236
|
}
|
|
207
237
|
export interface BuildResult {
|
|
208
238
|
files: Map<string, OutputContent>;
|
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
* - otherwise a page added, removed or changed, a nav file, or a file of the
|
|
9
9
|
* unit that is not a page (an include): the unit is built again without
|
|
10
10
|
* its pages (`rebuildUnitLazy`), its old output is dropped, and all its
|
|
11
|
-
* pages are stale
|
|
11
|
+
* pages are stale. What pass 1 read from each unchanged page is kept
|
|
12
|
+
* between rebuilds (src/core/scan-cache.ts), so this is cheap;
|
|
13
|
+
* - a topic map, a map it includes or a source's `nav:` file: the units
|
|
14
|
+
* that read it are built again, as above;
|
|
12
15
|
* - an asset: its entry is replaced;
|
|
13
|
-
* -
|
|
14
|
-
*
|
|
16
|
+
* - a config change to `attributes` only: every unit is built again, as above;
|
|
17
|
+
* - any other config change, the theme or extra stylesheets: everything is
|
|
18
|
+
* built again, and swapped in only if that build succeeds.
|
|
15
19
|
*
|
|
16
20
|
* A stale page is rendered when it is asked for, with the current nav and
|
|
17
21
|
* page table, so nothing shown is out of date and no dependency map is
|
|
@@ -32,9 +36,7 @@ export interface DevSession {
|
|
|
32
36
|
* already updated. It does not throw: a change that fails sets `buildError` (and `buildProblem`)
|
|
33
37
|
* and resolves `{ css: false }`, and the last good site stays served.
|
|
34
38
|
*/
|
|
35
|
-
change(paths: string[]): Promise<
|
|
36
|
-
css: boolean;
|
|
37
|
-
}>;
|
|
39
|
+
change(paths: string[]): Promise<ChangeResult>;
|
|
38
40
|
/** Warnings not handed out yet (the start's, then those of each change and page render). */
|
|
39
41
|
takeWarnings(): string[];
|
|
40
42
|
/**
|
|
@@ -62,6 +64,16 @@ export interface DevSession {
|
|
|
62
64
|
/** `buildError` as data: a config that does not parse names the config file, with its line and column. */
|
|
63
65
|
readonly buildProblem: BuildProblem | undefined;
|
|
64
66
|
}
|
|
67
|
+
/** What `change` did: `css` when the stylesheet may have changed, how it handled the change, and how long it took. */
|
|
68
|
+
export interface ChangeResult {
|
|
69
|
+
css: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The most it rebuilt: `full` (everything, as at the start), `unit` (one or more units without
|
|
72
|
+
* their pages), `page` (pages marked stale), `asset` (files replaced), `none` (nothing it builds).
|
|
73
|
+
*/
|
|
74
|
+
handled: "full" | "unit" | "page" | "asset" | "none";
|
|
75
|
+
ms: number;
|
|
76
|
+
}
|
|
65
77
|
/**
|
|
66
78
|
* By default every page is rendered at the start, as `build` does: search has its index and
|
|
67
79
|
* every warning is reported up front. `lazy`: only pass 1 runs at the start and each page is
|
|
@@ -15,7 +15,8 @@ export interface NavNode {
|
|
|
15
15
|
/** "topics/x.md" → "topics/x"; "index.md" → ""; "a/index.md" → "a". */
|
|
16
16
|
export declare function slugFromPath(p: string): string;
|
|
17
17
|
type LabelFor = (slug: string) => string | undefined;
|
|
18
|
-
|
|
18
|
+
/** `load`: the YAML loader, which a dev session's scan cache wraps (it reads the result, never changes it). */
|
|
19
|
+
export declare function parseNavYaml(text: string, labelFor: LabelFor, baseDir?: string, load?: (text: string) => unknown): NavNode[];
|
|
19
20
|
export declare function parseNavJson(text: string, labelFor: LabelFor): NavNode[];
|
|
20
21
|
/** Derive a nav tree from page slugs: one section per directory, index pages as landing pages. */
|
|
21
22
|
export declare function navFromPages(pages: {
|
|
@@ -1,6 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minja templating pass: `{{ var }}`, `{% if %}`, `{% include %}`.
|
|
3
|
+
* Runs on the raw page text before the markdown parser, the same way
|
|
4
|
+
* the Dogsbay CLI's site build does, but over an in-memory file map.
|
|
5
|
+
*
|
|
6
|
+
* Template syntax inside code (fences and inline spans) is masked so
|
|
7
|
+
* examples such as Helm's `{{ .Values }}` are never evaluated. `{#`
|
|
8
|
+
* (heading anchors like `{#intro}`) is masked globally because minja
|
|
9
|
+
* reads it as a comment opener.
|
|
10
|
+
*/
|
|
11
|
+
import { type ParseCache } from "@dogsbay/minja/browser";
|
|
1
12
|
export interface PreprocessResult {
|
|
2
13
|
text: string;
|
|
3
14
|
error?: string;
|
|
15
|
+
/** Not rendered: the build's template time budget was spent (counted in `TemplateBudget.skipped`). */
|
|
16
|
+
skipped?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Template time for a whole build (plans/worker-scale.md stage 3): each render gets the shorter of
|
|
20
|
+
* its own limit and what is left, and once it is spent the rest are not rendered (counted, for one
|
|
21
|
+
* problem). Time is counted while at least one render is running, so renders the build prepares
|
|
22
|
+
* ahead in parallel are not counted twice. A dev session resets it for each request.
|
|
23
|
+
*/
|
|
24
|
+
export declare class TemplateBudget {
|
|
25
|
+
readonly renderMs: number;
|
|
26
|
+
readonly buildMs: number;
|
|
27
|
+
private readonly now;
|
|
28
|
+
private spentMs;
|
|
29
|
+
private active;
|
|
30
|
+
private since;
|
|
31
|
+
/** Renders not run because the budget was spent, by what they were for. */
|
|
32
|
+
skipped: {
|
|
33
|
+
pages: number;
|
|
34
|
+
titles: number;
|
|
35
|
+
};
|
|
36
|
+
constructor(renderMs: number, buildMs: number, now?: () => number);
|
|
37
|
+
get spent(): number;
|
|
38
|
+
/** The time the next render may take; 0 when the budget is spent. */
|
|
39
|
+
next(): number;
|
|
40
|
+
start(): void;
|
|
41
|
+
end(): void;
|
|
42
|
+
reset(): void;
|
|
43
|
+
/** The problem to report when renders were skipped since the last reset, else undefined. */
|
|
44
|
+
problem(): string | undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Per render: render limits for a build (untrusted: 2 s; else 10 s), and for the whole build (untrusted: 30 s; else 10 minutes). */
|
|
47
|
+
export declare function templateBudget(untrusted: boolean, given?: {
|
|
48
|
+
renderMs?: number;
|
|
49
|
+
buildMs?: number;
|
|
50
|
+
}): TemplateBudget;
|
|
51
|
+
/** What a build's renders share: the parse cache for includes, the time budget, and what the render is for. */
|
|
52
|
+
export interface TemplateRun {
|
|
53
|
+
parseCache?: ParseCache | false;
|
|
54
|
+
budget?: TemplateBudget;
|
|
55
|
+
/** What the render is for: a page's body, a title, or a copy of a body already counted (llms-full.txt), not counted again. */
|
|
56
|
+
kind?: "page" | "title" | "copy";
|
|
4
57
|
}
|
|
5
58
|
/** True when a page contains template syntax worth running minja over. */
|
|
6
59
|
export declare function needsTemplating(text: string): boolean;
|
|
@@ -9,6 +62,6 @@ export declare function needsTemplating(text: string): boolean;
|
|
|
9
62
|
* to the content root; `files` looks up other files by the same kind
|
|
10
63
|
* of path. A template error leaves the page unchanged and is reported.
|
|
11
64
|
*/
|
|
12
|
-
export declare function preprocess(text: string, path: string, attributes: Record<string, unknown>, files: (path: string) => string | undefined | Promise<string | undefined>, undefinedRefs?: "preserve" | "asciidoc-literal"): Promise<PreprocessResult>;
|
|
65
|
+
export declare function preprocess(text: string, path: string, attributes: Record<string, unknown>, files: (path: string) => string | undefined | Promise<string | undefined>, undefinedRefs?: "preserve" | "asciidoc-literal", run?: TemplateRun): Promise<PreprocessResult>;
|
|
13
66
|
/** `product-title` is also reachable as `product_title`, because minja normalises hyphens on lookup. */
|
|
14
67
|
export declare function withUnderscoreAliases(attrs: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What pass 1 of a unit's build reads from each page, kept between a dev session's rebuilds
|
|
3
|
+
* (plans/worker-scale.md, stage 4). Rebuilding a unit after a menu, title, include or new-page
|
|
4
|
+
* edit re-reads every page; on a large site most of that is resolving templated titles, which
|
|
5
|
+
* renders each page's preamble and its includes. A page's entry is used again while its file is
|
|
6
|
+
* the same content (the dev server and the playground replace a changed file's value in the file
|
|
7
|
+
* map), its title was resolved with the same attributes, and no file its title read has changed.
|
|
8
|
+
*
|
|
9
|
+
* Only a dev session builds with one (`BuildHooks.keep` or `lazy`): a build reads each page once.
|
|
10
|
+
*/
|
|
11
|
+
import type { FileContent } from "./build.js";
|
|
12
|
+
import type { TopicMapNav } from "./topicmap.js";
|
|
13
|
+
export interface ScanEntry {
|
|
14
|
+
/** The file map's value the entry was made from: a different value is a different file. */
|
|
15
|
+
content: FileContent;
|
|
16
|
+
fm?: Record<string, unknown>;
|
|
17
|
+
/** The first H1 and the front matter block (`fmKey`), for telling a body edit from a page-table change. */
|
|
18
|
+
fmKey?: string;
|
|
19
|
+
/** Mermaid sources the page fences. */
|
|
20
|
+
diagrams?: string[];
|
|
21
|
+
/** The resolved title and description, and what they were resolved with. */
|
|
22
|
+
title?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
resolvedWith?: string;
|
|
25
|
+
/** Project paths the title and description read (includes found or looked for). */
|
|
26
|
+
deps?: Set<string>;
|
|
27
|
+
}
|
|
28
|
+
export declare class ScanCache {
|
|
29
|
+
private entries;
|
|
30
|
+
/** Project path of a file read while resolving titles → the pages whose entries read it. */
|
|
31
|
+
private readers;
|
|
32
|
+
/** The last nav YAML loaded, by its text: an unchanged nav.yml is not parsed again. */
|
|
33
|
+
private navText?;
|
|
34
|
+
private navData?;
|
|
35
|
+
/** Resolved topic maps, with what they were resolved with and the file map's value of every file read. */
|
|
36
|
+
private maps;
|
|
37
|
+
/** The page's entry when it was made from this content, else a new empty one for it. */
|
|
38
|
+
entry(path: string, content: FileContent): ScanEntry;
|
|
39
|
+
/** Records that a page's title or description read a file, so a change to that file drops them. */
|
|
40
|
+
read(page: string, file: string): void;
|
|
41
|
+
/** A file changed, was added or was removed: its own entry goes, and the titles that read it. */
|
|
42
|
+
changed(path: string): void;
|
|
43
|
+
/** `load(text)`, or the value it gave for the same text last time (the caller must not change it). */
|
|
44
|
+
yaml(text: string, load: (text: string) => unknown): unknown;
|
|
45
|
+
/** A topic map resolved with `key` (its variables), if no file it read has changed since. */
|
|
46
|
+
topicMap(path: string, key: string, files: Map<string, FileContent>): TopicMapNav | undefined;
|
|
47
|
+
/** Keeps a resolved topic map (not one with errors: the build stops on those) and the files it read. */
|
|
48
|
+
keepTopicMap(path: string, key: string, reads: Map<string, FileContent | undefined>, nav: TopicMapNav): void;
|
|
49
|
+
get size(): number;
|
|
50
|
+
private dropDeps;
|
|
51
|
+
}
|
|
@@ -20,8 +20,8 @@ export declare function openDevSession(input: BuildInput & {
|
|
|
20
20
|
}, o?: DevOptions): Promise<DevSession>;
|
|
21
21
|
export { prebuiltSiteCss };
|
|
22
22
|
export type { BuildInput, BuildResult, FileContent, OutputContent, SinkContent, SiteCssBuilder, BuiltPage, HighlightPool, BuildProblem, PageDiagnostic } from "../core/build.js";
|
|
23
|
-
export type { DevOptions, DevSession } from "../core/dev.js";
|
|
24
|
-
export { parseConfig, ConfigError, type SiteConfig, type DiagramEngine } from "../core/config.js";
|
|
23
|
+
export type { ChangeResult, DevOptions, DevSession } from "../core/dev.js";
|
|
24
|
+
export { parseConfig, projectRoots, ConfigError, type SiteConfig, type DiagramEngine } from "../core/config.js";
|
|
25
25
|
export type { DiagramRenderer, DiagramRenderers, DiagramRequest, DiagramResult } from "../core/diagrams.js";
|
|
26
26
|
export { peleRenderer } from "../core/pele.js";
|
|
27
27
|
export { parseNavYaml, parseNavJson, navFromPages, indexNav, type NavNode, type NavIndex } from "../core/nav.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/ssg",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.133",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Documentation static site generator for Dogsbay-MD that builds inside Cloudflare Workers and publishes sites that work offline",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -47,38 +47,38 @@
|
|
|
47
47
|
"tailwindcss": "^4.3.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@dogsbay/elements": "0.2.0-beta.
|
|
51
|
-
"@dogsbay/format-dogsbay-md": "0.2.0-beta.
|
|
52
|
-
"@dogsbay/format-openapi": "0.2.0-beta.
|
|
53
|
-
"@dogsbay/format-topicmap": "0.2.0-beta.
|
|
54
|
-
"@dogsbay/highlight": "0.2.0-beta.
|
|
55
|
-
"@dogsbay/minja": "0.2.0-beta.
|
|
56
|
-
"@dogsbay/primitives": "0.2.0-beta.
|
|
57
|
-
"@dogsbay/render-html": "0.2.0-beta.
|
|
58
|
-
"@dogsbay/search": "0.2.0-beta.
|
|
59
|
-
"@dogsbay/serialize-core": "0.2.0-beta.
|
|
60
|
-
"@dogsbay/site-check": "0.2.0-beta.
|
|
61
|
-
"@dogsbay/site-files": "0.2.0-beta.
|
|
62
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
50
|
+
"@dogsbay/elements": "0.2.0-beta.133",
|
|
51
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.133",
|
|
52
|
+
"@dogsbay/format-openapi": "0.2.0-beta.133",
|
|
53
|
+
"@dogsbay/format-topicmap": "0.2.0-beta.133",
|
|
54
|
+
"@dogsbay/highlight": "0.2.0-beta.133",
|
|
55
|
+
"@dogsbay/minja": "0.2.0-beta.133",
|
|
56
|
+
"@dogsbay/primitives": "0.2.0-beta.133",
|
|
57
|
+
"@dogsbay/render-html": "0.2.0-beta.133",
|
|
58
|
+
"@dogsbay/search": "0.2.0-beta.133",
|
|
59
|
+
"@dogsbay/serialize-core": "0.2.0-beta.133",
|
|
60
|
+
"@dogsbay/site-check": "0.2.0-beta.133",
|
|
61
|
+
"@dogsbay/site-files": "0.2.0-beta.133",
|
|
62
|
+
"@dogsbay/types": "0.2.0-beta.133"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@axe-core/playwright": "^4.12.1",
|
|
66
66
|
"@cloudflare/workers-types": "^5.20260921.1",
|
|
67
|
-
"@dogsbay/elements": "0.2.0-beta.
|
|
68
|
-
"@dogsbay/format-dogsbay-md": "0.2.0-beta.
|
|
69
|
-
"@dogsbay/format-openapi": "0.2.0-beta.
|
|
70
|
-
"@dogsbay/format-topicmap": "0.2.0-beta.
|
|
71
|
-
"@dogsbay/highlight": "0.2.0-beta.
|
|
72
|
-
"@dogsbay/icons": "0.2.0-beta.
|
|
73
|
-
"@dogsbay/minja": "0.2.0-beta.
|
|
74
|
-
"@dogsbay/primitives": "0.2.0-beta.
|
|
75
|
-
"@dogsbay/render-html": "0.2.0-beta.
|
|
76
|
-
"@dogsbay/search": "0.2.0-beta.
|
|
77
|
-
"@dogsbay/serialize-core": "0.2.0-beta.
|
|
78
|
-
"@dogsbay/site-check": "0.2.0-beta.
|
|
79
|
-
"@dogsbay/site-files": "0.2.0-beta.
|
|
80
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
81
|
-
"@dogsbay/ui": "0.2.0-beta.
|
|
67
|
+
"@dogsbay/elements": "0.2.0-beta.133",
|
|
68
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.133",
|
|
69
|
+
"@dogsbay/format-openapi": "0.2.0-beta.133",
|
|
70
|
+
"@dogsbay/format-topicmap": "0.2.0-beta.133",
|
|
71
|
+
"@dogsbay/highlight": "0.2.0-beta.133",
|
|
72
|
+
"@dogsbay/icons": "0.2.0-beta.133",
|
|
73
|
+
"@dogsbay/minja": "0.2.0-beta.133",
|
|
74
|
+
"@dogsbay/primitives": "0.2.0-beta.133",
|
|
75
|
+
"@dogsbay/render-html": "0.2.0-beta.133",
|
|
76
|
+
"@dogsbay/search": "0.2.0-beta.133",
|
|
77
|
+
"@dogsbay/serialize-core": "0.2.0-beta.133",
|
|
78
|
+
"@dogsbay/site-check": "0.2.0-beta.133",
|
|
79
|
+
"@dogsbay/site-files": "0.2.0-beta.133",
|
|
80
|
+
"@dogsbay/types": "0.2.0-beta.133",
|
|
81
|
+
"@dogsbay/ui": "0.2.0-beta.133",
|
|
82
82
|
"@mermanjs/web": "0.7.0",
|
|
83
83
|
"@playwright/test": "1.61.1",
|
|
84
84
|
"@types/node": "^22.10.0",
|