@dogsbay/ssg 0.2.0-beta.135 → 0.2.0-beta.139
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 +260 -44
- package/dist/package/types/core/build.d.ts +40 -6
- package/dist/package/types/core/config.d.ts +18 -1
- package/dist/package/types/core/dev.d.ts +4 -0
- package/dist/package/types/core/diagrams.d.ts +5 -5
- package/dist/package/types/core/preprocess.d.ts +10 -0
- package/package.json +32 -30
|
@@ -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.139 (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.139 (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.139 (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.139 (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.139 (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
|
@@ -143,7 +143,8 @@ function parseConfig(text, overrides = {}) {
|
|
|
143
143
|
locale: s.locale != null ? unitId("locale", s.locale) : void 0,
|
|
144
144
|
attributes: s.attributes ?? void 0,
|
|
145
145
|
templates: sourceTemplates(s.templates, String(s.path)),
|
|
146
|
-
...s.nav != null ? { nav: sourceNav(s.nav, String(s.path), s.from) } : {}
|
|
146
|
+
...s.nav != null ? { nav: sourceNav(s.nav, String(s.path), s.from) } : {},
|
|
147
|
+
...s.data != null ? { data: dataFolder(`content.sources (${String(s.path)}): data`, s.data) } : {}
|
|
147
148
|
}));
|
|
148
149
|
if (sources.length === 0) sources.push({ path: "content" });
|
|
149
150
|
const versions = (content.versions ?? raw.versions ?? []).map((v) => ({
|
|
@@ -243,6 +244,8 @@ function parseConfig(text, overrides = {}) {
|
|
|
243
244
|
codeBlockTitle: content.codeBlockTitle === false ? false : content.codeBlockTitle === "auto" ? "auto" : true,
|
|
244
245
|
imageCaptions: content.imageCaptions === "title" ? "title" : false,
|
|
245
246
|
html: htmlOf(content.html),
|
|
247
|
+
dataDir: content.data != null ? dataFolder("content.data", content.data) : "data",
|
|
248
|
+
dataDirSet: content.data != null,
|
|
246
249
|
plausible: typeof raw.analytics?.plausible?.domain === "string" ? { domain: raw.analytics.plausible.domain, scriptUrl: strOrUndef(raw.analytics.plausible.scriptUrl) } : void 0,
|
|
247
250
|
...overrides
|
|
248
251
|
};
|
|
@@ -466,7 +469,17 @@ function projectRoots(cfg) {
|
|
|
466
469
|
const roots = cfg.sources.map((s) => s.path === "." ? "" : s.path);
|
|
467
470
|
if (roots.includes("")) return [""];
|
|
468
471
|
const navDirs = cfg.sources.flatMap((s) => s.nav ? [s.nav.includes("/") ? s.nav.slice(0, s.nav.lastIndexOf("/")) : s.nav] : []);
|
|
469
|
-
|
|
472
|
+
const dataDirs = [cfg.dataDir, ...cfg.sources.flatMap((s) => s.data ? [s.data] : [])];
|
|
473
|
+
return [.../* @__PURE__ */ new Set([...roots, cfg.themeDir, ...cfg.css, ...navDirs, ...dataDirs])].filter(Boolean);
|
|
474
|
+
}
|
|
475
|
+
function dataFolder(what, v) {
|
|
476
|
+
if (typeof v !== "string" || !v.trim()) throw new ConfigError(`${what} must be a folder inside the project, such as ./data; got ${JSON.stringify(v)}`);
|
|
477
|
+
const p = insideProject(what, v.trim()).replace(/^(\.\/)+/, "").replace(/\/+$/, "");
|
|
478
|
+
if (!p || p === ".") throw new ConfigError(`${what} must be a folder inside the project, not the project itself (every file would be a data file)`);
|
|
479
|
+
return p;
|
|
480
|
+
}
|
|
481
|
+
function dataDirOf(cfg, s) {
|
|
482
|
+
return s.data ?? cfg.dataDir;
|
|
470
483
|
}
|
|
471
484
|
function sourceRoot(s) {
|
|
472
485
|
return s.path === "." ? "" : s.path.replace(/^\.\//, "").replace(/\/+$/, "") + "/";
|
|
@@ -1072,31 +1085,31 @@ var CLIENT_CANDIDATES = ['""', '"&"', '"\\\\$&")).join("|")})', '"\\\\$&")}"', '
|
|
|
1072
1085
|
var SITE_NOTICES = `Third-party software in this site's _dogsbay/ files (dogsbay.js, site.css) and pages
|
|
1073
1086
|
|
|
1074
1087
|
========================================================================
|
|
1075
|
-
@dogsbay/elements@0.2.0-beta.
|
|
1088
|
+
@dogsbay/elements@0.2.0-beta.139 (MIT)
|
|
1076
1089
|
https://github.com/dogsbay/dogsbay.git
|
|
1077
1090
|
|
|
1078
1091
|
Licensed under MIT; the package ships no licence file.
|
|
1079
1092
|
|
|
1080
1093
|
========================================================================
|
|
1081
|
-
@dogsbay/primitives@0.2.0-beta.
|
|
1094
|
+
@dogsbay/primitives@0.2.0-beta.139 (MIT)
|
|
1082
1095
|
https://github.com/dogsbay/dogsbay.git
|
|
1083
1096
|
|
|
1084
1097
|
Licensed under MIT; the package ships no licence file.
|
|
1085
1098
|
|
|
1086
1099
|
========================================================================
|
|
1087
|
-
@dogsbay/render-html@0.2.0-beta.
|
|
1100
|
+
@dogsbay/render-html@0.2.0-beta.139 (MIT)
|
|
1088
1101
|
https://github.com/dogsbay/dogsbay.git
|
|
1089
1102
|
|
|
1090
1103
|
Licensed under MIT; the package ships no licence file.
|
|
1091
1104
|
|
|
1092
1105
|
========================================================================
|
|
1093
|
-
@dogsbay/search@0.2.0-beta.
|
|
1106
|
+
@dogsbay/search@0.2.0-beta.139 (MIT)
|
|
1094
1107
|
https://github.com/dogsbay/dogsbay.git
|
|
1095
1108
|
|
|
1096
1109
|
Licensed under MIT; the package ships no licence file.
|
|
1097
1110
|
|
|
1098
1111
|
========================================================================
|
|
1099
|
-
@dogsbay/ui@0.2.0-beta.
|
|
1112
|
+
@dogsbay/ui@0.2.0-beta.139 (MIT)
|
|
1100
1113
|
https://github.com/dogsbay/dogsbay.git
|
|
1101
1114
|
|
|
1102
1115
|
Licensed under MIT; the package ships no licence file.
|
|
@@ -2253,6 +2266,7 @@ async function preprocess(text, path, attributes, files, undefinedRefs = "preser
|
|
|
2253
2266
|
};
|
|
2254
2267
|
if (budget && timeout <= 0) return skip();
|
|
2255
2268
|
budget?.start();
|
|
2269
|
+
const warnings = [];
|
|
2256
2270
|
try {
|
|
2257
2271
|
const out = await render(mask(text), {
|
|
2258
2272
|
context: withUnderscoreAliases(attributes),
|
|
@@ -2262,9 +2276,14 @@ async function preprocess(text, path, attributes, files, undefinedRefs = "preser
|
|
|
2262
2276
|
basePath: dirname(path),
|
|
2263
2277
|
loader: new MapLoader(files),
|
|
2264
2278
|
timeout,
|
|
2265
|
-
...run.parseCache !== void 0 ? { parseCache: run.parseCache } : {}
|
|
2279
|
+
...run.parseCache !== void 0 ? { parseCache: run.parseCache } : {},
|
|
2280
|
+
// Once per tag and place: a page that includes a file twice is told once.
|
|
2281
|
+
onWarning: (w) => {
|
|
2282
|
+
if (!warnings.some((x) => x.tag === w.tag && x.line === w.line && x.include === w.include && x.variable === w.variable))
|
|
2283
|
+
warnings.push({ tag: w.tag, line: w.line, ...w.include ? { include: w.include } : {}, ...w.variable ? { variable: w.variable } : {} });
|
|
2284
|
+
}
|
|
2266
2285
|
});
|
|
2267
|
-
return { text: stripFenceMinjaMarkers(stripInlineMinjaMarkers(unmask(out))) };
|
|
2286
|
+
return { text: stripFenceMinjaMarkers(stripInlineMinjaMarkers(unmask(out))), ...warnings.length ? { warnings } : {} };
|
|
2268
2287
|
} catch (err) {
|
|
2269
2288
|
if (budget && timeout < budget.renderMs && err instanceof RenderLimitError && budget.next() <= 0) return skip();
|
|
2270
2289
|
return { text, error: err instanceof Error ? err.message : String(err) };
|
|
@@ -2360,7 +2379,7 @@ var ScanCache = class {
|
|
|
2360
2379
|
};
|
|
2361
2380
|
|
|
2362
2381
|
// src/core/build.ts
|
|
2363
|
-
import { TemplatePages } from "@dogsbay/format-dogsbay-md/browser";
|
|
2382
|
+
import { DATA_DIRECTIVES, DataCache, DataFileError, DataFiles, DEFAULT_DATA_LIMITS, hasDataDirectives, NO_PROJECT_MESSAGE, resolveDataDirectives, TemplatePages, UNTRUSTED_DATA_LIMITS } from "@dogsbay/format-dogsbay-md/browser";
|
|
2364
2383
|
import { extractText, SearchIndexBuilder } from "@dogsbay/search/index";
|
|
2365
2384
|
|
|
2366
2385
|
// src/core/strings.ts
|
|
@@ -2981,7 +3000,7 @@ function reporter(warnings, problems) {
|
|
|
2981
3000
|
problems.push({ code, ...at, ...at.file !== void 0 ? { file: detach(at.file) } : {}, message, text: t });
|
|
2982
3001
|
};
|
|
2983
3002
|
}
|
|
2984
|
-
function linedDiagnostics(found, raw, body, parsed) {
|
|
3003
|
+
function linedDiagnostics(found, raw, body, parsed, after = "of the page after templating") {
|
|
2985
3004
|
if (!found.length) return [];
|
|
2986
3005
|
const offset = raw.slice(0, raw.length - body.length).split("\n").length - 1;
|
|
2987
3006
|
const LINE = /\bline (\d+)/g;
|
|
@@ -2999,10 +3018,34 @@ function linedDiagnostics(found, raw, body, parsed) {
|
|
|
2999
3018
|
return found.map((d) => {
|
|
3000
3019
|
const i = asWritten.findIndex((w) => key(w) === key(d));
|
|
3001
3020
|
if (i >= 0) return lined(asWritten.splice(i, 1)[0]);
|
|
3002
|
-
return { code: d.code, message: detach(d.message.replace(LINE,
|
|
3021
|
+
return { code: d.code, message: detach(d.message.replace(LINE, `line $1 ${after}`)), ...named(d) };
|
|
3003
3022
|
});
|
|
3004
3023
|
}
|
|
3005
3024
|
var decoder = new TextDecoder();
|
|
3025
|
+
var DATA_DIRECTIVE_NODES = new Set(DATA_DIRECTIVES);
|
|
3026
|
+
var DEFAULT_DATA_CACHE_BYTES = 32 * 1024 * 1024;
|
|
3027
|
+
var NO_DATA_FOLDER = "this site has no data folder (data/ beside dogsbay.config.yml, or content.data)";
|
|
3028
|
+
function hasFilesUnder(files, dir) {
|
|
3029
|
+
const prefix = `${dir}/`;
|
|
3030
|
+
for (const k of files.keys()) if (k.startsWith(prefix)) return true;
|
|
3031
|
+
return false;
|
|
3032
|
+
}
|
|
3033
|
+
function strictestData(floor, given) {
|
|
3034
|
+
const out = { ...floor };
|
|
3035
|
+
for (const k of Object.keys(floor)) {
|
|
3036
|
+
const g = given?.[k];
|
|
3037
|
+
if (typeof g === "number" && Number.isFinite(g) && g >= 0 && g < floor[k]) out[k] = g;
|
|
3038
|
+
}
|
|
3039
|
+
return out;
|
|
3040
|
+
}
|
|
3041
|
+
function finiteLimits(given) {
|
|
3042
|
+
const out = {};
|
|
3043
|
+
for (const [k, v] of Object.entries(given ?? {})) if (typeof v === "number" && Number.isFinite(v) && v >= 0) out[k] = v;
|
|
3044
|
+
return out;
|
|
3045
|
+
}
|
|
3046
|
+
function inDataFolder(cfg, path) {
|
|
3047
|
+
return [cfg.dataDir, ...cfg.sources.flatMap((s) => s.data ? [s.data] : [])].some((d) => path.startsWith(`${d}/`));
|
|
3048
|
+
}
|
|
3006
3049
|
async function readText(c) {
|
|
3007
3050
|
if (typeof c === "string") return c;
|
|
3008
3051
|
if (isLazyText(c)) return c.read();
|
|
@@ -3082,13 +3125,16 @@ async function build(input, hooks = {}) {
|
|
|
3082
3125
|
const problems = [];
|
|
3083
3126
|
const report = reporter(warnings, problems);
|
|
3084
3127
|
for (const m of ignored) report("untrusted", `[untrusted] ${m}`);
|
|
3128
|
+
for (const [what, dir] of [...cfg.dataDirSet ? [["content.data", cfg.dataDir]] : [], ...cfg.sources.flatMap((s) => s.data ? [[`content.sources (${s.path}) data`, s.data]] : [])]) {
|
|
3129
|
+
if (!hasFilesUnder(input.files, dir)) report("data", `[data] ${what}: ${dir} has no files, so its data directives and data.<name> lookups find nothing`);
|
|
3130
|
+
}
|
|
3085
3131
|
for (const p of cfg.unsupportedPlugins) report("plugins", `[plugins] ${p} is an Astro-pipeline plugin; dogsbay-ssg ignores it`);
|
|
3086
3132
|
const highlighter = input.highlighter ?? await (await import("@dogsbay/highlight")).createHighlighter({ cacheChars: input.highlightCacheChars });
|
|
3087
3133
|
const buildCss = await siteCssBuilder(input);
|
|
3088
3134
|
phase.init = now() - t0;
|
|
3089
3135
|
let pages = 0;
|
|
3090
3136
|
let assets = 0;
|
|
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) };
|
|
3137
|
+
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), dataCache: new DataCache(input.dataCacheBytes ?? DEFAULT_DATA_CACHE_BYTES) };
|
|
3092
3138
|
for (const r of cfg.redirects) {
|
|
3093
3139
|
const from = sitePath(r.from, cfg.basePath);
|
|
3094
3140
|
if (!from) {
|
|
@@ -3279,8 +3325,8 @@ async function buildUnit(unit, allUnits, cfg, input, highlighter, emit, report,
|
|
|
3279
3325
|
const prefix = unit.prefix ? unit.prefix + "/" : "";
|
|
3280
3326
|
const label = unit.prefix || "/";
|
|
3281
3327
|
const warn = (code, m, at = {}) => report(code, `[${label}] ${m}`, { unit: label, ...at, message: at.message ?? m });
|
|
3282
|
-
const warnFile = (code, shown, file, m, line) => warn(code, `${shown}: ${m}`, { file, message: m, ...line ? { line } : {} });
|
|
3283
|
-
const warnPage = (info, code, m, line) => warnFile(code, where(info), srcOf(info), m, line);
|
|
3328
|
+
const warnFile = (code, shown, file, m, line, more = {}) => warn(code, `${shown}: ${m}`, { file, message: m, ...line ? { line } : {}, ...more });
|
|
3329
|
+
const warnPage = (info, code, m, line, more) => warnFile(code, where(info), srcOf(info), m, line, more);
|
|
3284
3330
|
const navFileAt = () => navProjectFiles.length === 1 ? { file: navProjectFiles[0] } : {};
|
|
3285
3331
|
const diagramWarn = (m, file) => file ? warnFile("diagram", file, abs(file), m) : warn("diagram", m);
|
|
3286
3332
|
let math;
|
|
@@ -3385,13 +3431,54 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3385
3431
|
if (k === void 0) resolvedKeys.set(key, k = JSON.stringify([label, key, sourceOf(p).templates ?? null, cfg.name, cfg.undefinedRefs, attributesOf(p)]));
|
|
3386
3432
|
return k;
|
|
3387
3433
|
};
|
|
3388
|
-
const
|
|
3434
|
+
const dataLimits = input.untrusted ? strictestData(UNTRUSTED_DATA_LIMITS, input.dataLimits) : { ...DEFAULT_DATA_LIMITS, ...finiteLimits(input.dataLimits) };
|
|
3435
|
+
const pageDataBytes = Math.min(dataLimits.maxTotalBytes, 4 * dataLimits.maxFileBytes);
|
|
3436
|
+
let dataDirHas = /* @__PURE__ */ new Map();
|
|
3437
|
+
const dataDir = (p) => {
|
|
3438
|
+
const dir2 = dataDirOf(cfg, sourceOf(p));
|
|
3439
|
+
let has = dataDirHas.get(dir2);
|
|
3440
|
+
if (has === void 0) dataDirHas.set(dir2, has = hasFilesUnder(input.files, dir2));
|
|
3441
|
+
return has ? dir2 : void 0;
|
|
3442
|
+
};
|
|
3443
|
+
const dataReader = (dir2, onLoad) => new DataFiles({
|
|
3444
|
+
limits: dataLimits,
|
|
3445
|
+
cache: state.dataCache,
|
|
3446
|
+
load: async (rel2) => {
|
|
3447
|
+
const c = input.files.get(`${dir2}/${rel2}`);
|
|
3448
|
+
if (c == null) return void 0;
|
|
3449
|
+
const text = await readText(c);
|
|
3450
|
+
onLoad?.(text.length, rel2);
|
|
3451
|
+
return text;
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3454
|
+
let dataBy = /* @__PURE__ */ new Map();
|
|
3455
|
+
const dataOf = (p) => {
|
|
3456
|
+
const dir2 = dataDir(p);
|
|
3457
|
+
if (!dir2) return void 0;
|
|
3458
|
+
let d = dataBy.get(dir2);
|
|
3459
|
+
if (!d) dataBy.set(dir2, d = dataReader(dir2));
|
|
3460
|
+
return d;
|
|
3461
|
+
};
|
|
3462
|
+
const pageDataOf = (p) => {
|
|
3463
|
+
const dir2 = dataDir(p);
|
|
3464
|
+
if (!dir2) return void 0;
|
|
3465
|
+
let read2 = 0;
|
|
3466
|
+
return dataReader(dir2, (bytes, rel2) => {
|
|
3467
|
+
read2 += bytes;
|
|
3468
|
+
if (read2 > pageDataBytes) throw new DataFileError(`this page's data directives read more than ${pageDataBytes.toLocaleString("en")} bytes of data in all (each directive counts its file again); split the page or the file`, rel2);
|
|
3469
|
+
});
|
|
3470
|
+
};
|
|
3471
|
+
const dataUnresolved = /* @__PURE__ */ new Set();
|
|
3472
|
+
const dataReads = /* @__PURE__ */ new Map();
|
|
3473
|
+
const dataTemplatePages = /* @__PURE__ */ new Set();
|
|
3474
|
+
let dataInTitles = false;
|
|
3475
|
+
let templatesBy = /* @__PURE__ */ new Map();
|
|
3389
3476
|
const templatesOf = (p) => {
|
|
3390
3477
|
const key = named.length ? namespaceOf(p) : "";
|
|
3391
3478
|
let tp = templatesBy.get(key);
|
|
3392
3479
|
if (!tp) {
|
|
3393
3480
|
const at = scoped(p);
|
|
3394
|
-
tp = new TemplatePages({ load: (q) => readInclude(at ? at(q) : q), attributes: attributesOf(p), sourceDefault: sourceOf(p).templates === "jinja" });
|
|
3481
|
+
tp = new TemplatePages({ load: (q) => readInclude(at ? at(q) : q), attributes: attributesOf(p), sourceDefault: sourceOf(p).templates === "jinja", data: dataOf(p) });
|
|
3395
3482
|
templatesBy.set(key, tp);
|
|
3396
3483
|
}
|
|
3397
3484
|
return tp;
|
|
@@ -3409,6 +3496,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3409
3496
|
const leftOut = /* @__PURE__ */ new Map();
|
|
3410
3497
|
for (const [p, c] of files) {
|
|
3411
3498
|
if (buildOnly(p)) continue;
|
|
3499
|
+
if (inDataFolder(cfg, abs(p))) continue;
|
|
3412
3500
|
if (/\.(md|markdown)$/i.test(p)) {
|
|
3413
3501
|
if (excluded(p)) {
|
|
3414
3502
|
leftOut.set(slugFromPath(p), cfg.excludeFromRoutes.find((d) => p === d || p.startsWith(d.replace(/\/+$/, "") + "/")) ?? "_ or . files");
|
|
@@ -3473,6 +3561,8 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3473
3561
|
for (const f of memo2.deps ?? []) includedProject.add(f);
|
|
3474
3562
|
} else {
|
|
3475
3563
|
const keep = memo2 && !templatesOf(p).isTemplatePage(fm);
|
|
3564
|
+
const titled = `${str(fm.title) ?? ""} ${fm.description ?? ""}`;
|
|
3565
|
+
if (templatesOf(p).isTemplatePage(fm) && /\{[{%]/.test(titled) && /\bdata\s*(\.[A-Za-z_]|\[)/.test(titled)) dataInTitles = true;
|
|
3476
3566
|
const reads = keep ? (f) => state.scan.read(abs(p), f) : void 0;
|
|
3477
3567
|
const skippedBefore = state.templateBudget.skipped.titles;
|
|
3478
3568
|
title = detach(await resolveTitle(str(fm.title) ?? firstH1(await source()) ?? humanize2(slug.split("/").pop() || cfg.name), await source(), p, fm, reads));
|
|
@@ -3708,7 +3798,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3708
3798
|
const brokenCounts = /* @__PURE__ */ new Map();
|
|
3709
3799
|
const prepare = async (info) => {
|
|
3710
3800
|
const warned = [];
|
|
3711
|
-
const warn2 = (code, m, line) => warned.push([code, m, line]);
|
|
3801
|
+
const warn2 = (code, m, line, more) => warned.push([code, m, line, more]);
|
|
3712
3802
|
let diagnostics;
|
|
3713
3803
|
let t2 = now();
|
|
3714
3804
|
if (info.virtual) {
|
|
@@ -3730,18 +3820,54 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3730
3820
|
warn2("front-matter", `front matter does not parse${line ? ` (line ${line})` : ""}: ${reason}`, line);
|
|
3731
3821
|
if (mode.diagnostics) diagnostics = [{ code: "front-matter", message: detach(`front matter does not parse: ${reason}`), ...line ? { line } : {} }];
|
|
3732
3822
|
}
|
|
3733
|
-
const
|
|
3823
|
+
const reads = /* @__PURE__ */ new Set();
|
|
3824
|
+
const readDir = dataDir(info.source);
|
|
3825
|
+
const isTemplate = templatesOf(info.source).isTemplatePage(fm);
|
|
3826
|
+
if (isTemplate && readDir) dataTemplatePages.add(info.source);
|
|
3827
|
+
else dataTemplatePages.delete(info.source);
|
|
3828
|
+
const pre = isTemplate ? { text: await templatesOf(info.source).render(bodySrc, info.source, fm, { firstLine: raw.slice(0, raw.length - bodySrc.length).split("\n").length, onDataRead: (d) => reads.add(`${readDir}/${d}`) }) } : await preprocess(bodySrc, info.source, attributesOf(info.source), includesFor(info.source), cfg.undefinedRefs, templateRun);
|
|
3734
3829
|
if (pre.error) {
|
|
3735
3830
|
warn2("template", `template error: ${pre.error}`);
|
|
3736
3831
|
if (mode.diagnostics) (diagnostics ??= []).push({ code: "template", message: detach(`template error: ${pre.error}`) });
|
|
3737
3832
|
}
|
|
3833
|
+
if ("warnings" in pre && pre.warnings) {
|
|
3834
|
+
const fmLines = raw.slice(0, raw.length - bodySrc.length).split("\n").length - 1;
|
|
3835
|
+
for (const w of pre.warnings) {
|
|
3836
|
+
if (w.tag.startsWith("end")) continue;
|
|
3837
|
+
const where2 = w.include ? ` (in ${w.include})` : w.variable ? ` (in the value of {{ ${w.variable} }})` : "";
|
|
3838
|
+
const m = `{% ${w.tag} %} is not a tag of an ordinary page and is shown as written${where2}; for loops and the like, make it a template page (template: jinja in its front matter)`;
|
|
3839
|
+
const line = w.include || w.variable ? void 0 : w.line + fmLines;
|
|
3840
|
+
warn2("template-tag", m, line);
|
|
3841
|
+
if (mode.diagnostics) (diagnostics ??= []).push({ code: "template-tag", message: detach(m), ...line ? { line } : {} });
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
const templated = pre.text;
|
|
3845
|
+
dataUnresolved.delete(info.source);
|
|
3846
|
+
if (hasDataDirectives(pre.text)) {
|
|
3847
|
+
const data = pageDataOf(info.source);
|
|
3848
|
+
const r = await resolveDataDirectives(pre.text, data);
|
|
3849
|
+
if (r.problems.length) dataUnresolved.add(info.source);
|
|
3850
|
+
for (const d of r.reads) reads.add(`${readDir}/${d}`);
|
|
3851
|
+
const offset = pre.text === bodySrc ? raw.slice(0, raw.length - bodySrc.length).split("\n").length - 1 : void 0;
|
|
3852
|
+
for (const pr of r.problems) {
|
|
3853
|
+
const message = data ? pr.message : pr.message.replace(NO_PROJECT_MESSAGE, NO_DATA_FOLDER);
|
|
3854
|
+
const line = offset === void 0 ? void 0 : pr.line + offset;
|
|
3855
|
+
const shown = line === void 0 ? `${message} (line ${pr.line} of the page after templating)` : message;
|
|
3856
|
+
const inFile = pr.file && readDir && data && !/(^|\/)\.\.?(\/|$)/.test(pr.file) && input.files.has(`${readDir}/${pr.file}`) ? { dataFile: `${readDir}/${pr.file}`, ...pr.fileLine ? { dataLine: pr.fileLine } : {} } : {};
|
|
3857
|
+
warn2("data", shown, line, inFile);
|
|
3858
|
+
if (mode.diagnostics) (diagnostics ??= []).push({ code: "data", message: detach(shown), ...line ? { line } : {} });
|
|
3859
|
+
}
|
|
3860
|
+
pre.text = r.text;
|
|
3861
|
+
}
|
|
3862
|
+
if (reads.size) dataReads.set(info.source, reads);
|
|
3863
|
+
else dataReads.delete(info.source);
|
|
3738
3864
|
phase.template = (phase.template ?? 0) + now() - t2;
|
|
3739
3865
|
t2 = now();
|
|
3740
3866
|
let tree;
|
|
3741
3867
|
try {
|
|
3742
3868
|
const parsed = dogsbayMdToTree(pre.text, mode.diagnostics ? { diagnostics: true } : void 0);
|
|
3743
3869
|
tree = parsed.tree;
|
|
3744
|
-
if (mode.diagnostics) (diagnostics ??= []).push(...linedDiagnostics(parsed.diagnostics ?? [], raw, bodySrc, pre.text));
|
|
3870
|
+
if (mode.diagnostics) (diagnostics ??= []).push(...linedDiagnostics(parsed.diagnostics ?? [], raw, bodySrc, pre.text, templated === bodySrc ? "of the page with its data directives drawn" : void 0));
|
|
3745
3871
|
} catch (e) {
|
|
3746
3872
|
warn2("parse", `parse error: ${e.message}`);
|
|
3747
3873
|
if (mode.diagnostics) (diagnostics ??= []).push({ code: "parse", message: detach(`parse error: ${e.message}`) });
|
|
@@ -3759,50 +3885,100 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3759
3885
|
const markdown = (unresolved || ns) && cfg.mdMirror ? treeToDogsbayMd(tree) : pre.text;
|
|
3760
3886
|
return { raw, fm, bodySrc, pre, tree, warned, markdown, diagnostics };
|
|
3761
3887
|
};
|
|
3762
|
-
const
|
|
3888
|
+
const pagesOpt = Math.floor(input.highlightAhead?.pages ?? 24);
|
|
3889
|
+
const aheadPages = Number.isNaN(pagesOpt) ? 24 : Math.max(1, pagesOpt);
|
|
3890
|
+
const aheadChars = input.highlightAhead?.chars ?? Infinity;
|
|
3891
|
+
const charBound = Number.isFinite(aheadChars);
|
|
3892
|
+
const keyOf = (code, lang2, opts) => `${lang2 ?? ""}\0${JSON.stringify(opts)}\0${code}`;
|
|
3893
|
+
const delivered = /* @__PURE__ */ new Map();
|
|
3894
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
3895
|
+
const pageHighlighter = input.highlightPool ? {
|
|
3896
|
+
render: (c, l, o) => highlighter.render(c, l, o),
|
|
3897
|
+
code: (c, l, o) => delivered.get(keyOf(c, l, o)) ?? highlighter.code(c, l, o),
|
|
3898
|
+
resolve: (l) => highlighter.resolve(l),
|
|
3899
|
+
has: (c, l, o) => delivered.has(keyOf(c, l, o)) || highlighter.has(c, l, o),
|
|
3900
|
+
prime: (c, l, o, h) => highlighter.prime(c, l, o, h),
|
|
3901
|
+
cacheStats: () => highlighter.cacheStats(),
|
|
3902
|
+
load: highlighter.load && ((b) => highlighter.load(b))
|
|
3903
|
+
} : highlighter;
|
|
3763
3904
|
const renderAhead = async (all, pool) => {
|
|
3764
3905
|
const list = input.onPageBuilt ? all : all.filter((info) => !(replaced != null && info.slug === replaced));
|
|
3765
3906
|
const inflight = /* @__PURE__ */ new Map();
|
|
3766
3907
|
const ahead = /* @__PURE__ */ new Map();
|
|
3767
|
-
|
|
3908
|
+
let cur = 0;
|
|
3909
|
+
let next = 0;
|
|
3910
|
+
let held = 0;
|
|
3911
|
+
let preparing = 0;
|
|
3912
|
+
const fill = () => {
|
|
3913
|
+
while (next < list.length && next < cur + aheadPages && (next <= cur + 1 || !charBound || preparing === 0 && held < aheadChars)) start(next++);
|
|
3914
|
+
};
|
|
3768
3915
|
const start = (i) => {
|
|
3769
|
-
|
|
3770
|
-
const
|
|
3916
|
+
preparing++;
|
|
3917
|
+
const prepared = prepare(list[i]).finally(() => preparing--);
|
|
3918
|
+
const job = prepared.then(async (p) => {
|
|
3919
|
+
held += p.raw.length;
|
|
3771
3920
|
const waits = [];
|
|
3772
3921
|
const send = [];
|
|
3922
|
+
const keys = /* @__PURE__ */ new Map();
|
|
3773
3923
|
const sendKeys = /* @__PURE__ */ new Set();
|
|
3774
3924
|
for (const r of codeRequests(p.tree, !!diagrams)) {
|
|
3775
|
-
const k = keyOf(r);
|
|
3776
|
-
|
|
3777
|
-
if (
|
|
3778
|
-
|
|
3925
|
+
const k = keyOf(r.code, r.lang, r.opts);
|
|
3926
|
+
if (keys.has(k)) continue;
|
|
3927
|
+
if (delivered.has(k) || inflight.has(k)) {
|
|
3928
|
+
keys.set(k, r);
|
|
3929
|
+
const on = inflight.get(k);
|
|
3930
|
+
if (on) waits.push(on);
|
|
3931
|
+
} else if (!highlighter.has(r.code, r.lang, r.opts)) {
|
|
3932
|
+
keys.set(k, r);
|
|
3779
3933
|
send.push(r);
|
|
3780
3934
|
sendKeys.add(k);
|
|
3781
3935
|
}
|
|
3782
3936
|
}
|
|
3937
|
+
for (const [k, r] of keys) {
|
|
3938
|
+
const w = wanted.get(k);
|
|
3939
|
+
if (w) w.n++;
|
|
3940
|
+
else wanted.set(k, { r, n: 1 });
|
|
3941
|
+
}
|
|
3783
3942
|
if (send.length) {
|
|
3784
|
-
const
|
|
3785
|
-
(html) => send.forEach((r, n) =>
|
|
3943
|
+
const done2 = pool.render(send).then(
|
|
3944
|
+
(html) => send.forEach((r, n) => {
|
|
3945
|
+
const k = keyOf(r.code, r.lang, r.opts);
|
|
3946
|
+
if (typeof html[n] === "string" && wanted.has(k) && !delivered.has(k)) {
|
|
3947
|
+
delivered.set(k, html[n]);
|
|
3948
|
+
held += html[n].length;
|
|
3949
|
+
}
|
|
3950
|
+
}),
|
|
3786
3951
|
() => {
|
|
3787
3952
|
}
|
|
3788
|
-
);
|
|
3789
|
-
const done2 = delivered.finally(() => sendKeys.forEach((k) => inflight.delete(k)));
|
|
3953
|
+
).finally(() => sendKeys.forEach((k) => inflight.delete(k)));
|
|
3790
3954
|
for (const k of sendKeys) inflight.set(k, done2);
|
|
3791
3955
|
waits.push(done2);
|
|
3792
3956
|
}
|
|
3957
|
+
fill();
|
|
3793
3958
|
await Promise.all(waits);
|
|
3794
|
-
return p;
|
|
3959
|
+
return { p, keys };
|
|
3795
3960
|
});
|
|
3796
3961
|
job.catch(() => {
|
|
3797
3962
|
});
|
|
3798
3963
|
ahead.set(i, job);
|
|
3799
3964
|
};
|
|
3800
3965
|
try {
|
|
3801
|
-
for (
|
|
3802
|
-
|
|
3803
|
-
const p = await ahead.get(
|
|
3804
|
-
ahead.delete(
|
|
3805
|
-
await renderPage(list[
|
|
3966
|
+
for (cur = 0; cur < list.length; cur++) {
|
|
3967
|
+
fill();
|
|
3968
|
+
const { p, keys } = await ahead.get(cur);
|
|
3969
|
+
ahead.delete(cur);
|
|
3970
|
+
await renderPage(list[cur], p);
|
|
3971
|
+
held -= p.raw.length;
|
|
3972
|
+
for (const k of keys.keys()) {
|
|
3973
|
+
const w = wanted.get(k);
|
|
3974
|
+
if (--w.n) continue;
|
|
3975
|
+
wanted.delete(k);
|
|
3976
|
+
const html = delivered.get(k);
|
|
3977
|
+
if (html === void 0) continue;
|
|
3978
|
+
delivered.delete(k);
|
|
3979
|
+
held -= html.length;
|
|
3980
|
+
highlighter.prime(w.r.code, w.r.lang, w.r.opts, html);
|
|
3981
|
+
}
|
|
3806
3982
|
}
|
|
3807
3983
|
} finally {
|
|
3808
3984
|
for (const p of ahead.values()) p.catch(() => {
|
|
@@ -3813,7 +3989,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3813
3989
|
const isReplaced = replaced != null && info.slug === replaced;
|
|
3814
3990
|
if (isReplaced && !input.onPageBuilt) return;
|
|
3815
3991
|
const got = prepared ?? await prepare(info);
|
|
3816
|
-
for (const [code, m, line] of got.warned) warnPage(info, code, m, line);
|
|
3992
|
+
for (const [code, m, line, more] of got.warned) warnPage(info, code, m, line, more);
|
|
3817
3993
|
mode.diagnostics?.(srcOf(info), got.diagnostics ?? []);
|
|
3818
3994
|
const { raw, fm, bodySrc, pre, markdown } = got;
|
|
3819
3995
|
let tree = got.tree;
|
|
@@ -3852,7 +4028,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3852
4028
|
const noteRemoved = (code, m) => void (code !== "image-remote" && removed.add(m));
|
|
3853
4029
|
if (sanitizing) tree = sanitizeTree(tree, noteRemoved);
|
|
3854
4030
|
if (cfg.math && !math && mayHaveMath(tree)) math = await loadMath();
|
|
3855
|
-
await highlighter.load?.(highlightInputs(tree));
|
|
4031
|
+
if (!input.highlightPool || codeRequests(tree, !!diagrams).some((r) => !pageHighlighter.has(r.code, r.lang, r.opts))) await highlighter.load?.(highlightInputs(tree));
|
|
3856
4032
|
const unloadedBefore = highlighter.cacheStats().unloaded ?? 0;
|
|
3857
4033
|
t = now();
|
|
3858
4034
|
const apiLayout = apiPageLayout(tree);
|
|
@@ -3860,7 +4036,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3860
4036
|
const ctx = createRenderContext({
|
|
3861
4037
|
link: resolver.link,
|
|
3862
4038
|
asset: resolver.asset,
|
|
3863
|
-
highlighter,
|
|
4039
|
+
highlighter: pageHighlighter,
|
|
3864
4040
|
// The layout's own ids, outside the rendered content: generated ids avoid them
|
|
3865
4041
|
reservedIds: ["db-content", "db-sidebar", "db-search", "db-search-results", "db-toc-title", "db-page-markdown"],
|
|
3866
4042
|
t: t_,
|
|
@@ -3894,7 +4070,7 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
3894
4070
|
if (navNode?.children.length && !hasProse(tree)) body += childLinks2(navNode.children, (s) => pageRefs.get(s), info.out, cfg.linkStyle, infos);
|
|
3895
4071
|
const layoutName = str(fm.layout) ?? (post ? "blank" : cfg.layout);
|
|
3896
4072
|
const tocMode = fm.toc === false ? "off" : ["top", "popover", "rail", "off"].includes(fm.toc) ? fm.toc : cfg.toc;
|
|
3897
|
-
for (const u of ctx.unknown) warnPage(info, "unsupported-node", `unsupported node "${u}" rendered as a plain container`);
|
|
4073
|
+
for (const u of ctx.unknown) if (!(DATA_DIRECTIVE_NODES.has(u) && dataUnresolved.has(info.source))) warnPage(info, "unsupported-node", `unsupported node "${u}" rendered as a plain container`);
|
|
3898
4074
|
for (const u of ctx.unsafeUrls ?? []) warnPage(info, "unsafe-url", `refused the unsafe URL "${u}"`);
|
|
3899
4075
|
for (const m of removed) warnPage(info, "html", m);
|
|
3900
4076
|
for (const name of ctx.missingIcons ?? []) warnPage(info, "icon", `unknown icon "${name}"${input.icons ? "" : " (only the built-in icons are available in this build)"}`);
|
|
@@ -4081,7 +4257,8 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
4081
4257
|
body = sp.content;
|
|
4082
4258
|
} catch {
|
|
4083
4259
|
}
|
|
4084
|
-
const
|
|
4260
|
+
const templated = 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;
|
|
4261
|
+
const text = hasDataDirectives(templated) ? (await resolveDataDirectives(templated, pageDataOf(info.source))).text : templated;
|
|
4085
4262
|
const ns = named.length ? namespaceOf(info.source) : "";
|
|
4086
4263
|
if (!ns && !PLACEHOLDER_DIRECTIVE.test(text)) return text;
|
|
4087
4264
|
const tree = dogsbayMdToTree(text).tree;
|
|
@@ -4145,6 +4322,26 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
|
|
|
4145
4322
|
included(path) {
|
|
4146
4323
|
const r = rel(path);
|
|
4147
4324
|
return r !== void 0 && includedPaths.has(r) || includedProject.has(path);
|
|
4325
|
+
},
|
|
4326
|
+
dataReaders(path) {
|
|
4327
|
+
const stem = (p) => p.replace(/\.[A-Za-z]+$/, "");
|
|
4328
|
+
const out = [];
|
|
4329
|
+
for (const info of bySource.values()) {
|
|
4330
|
+
if (info.slug === replaced) continue;
|
|
4331
|
+
const dir2 = dataDirOf(cfg, sourceOf(info.source));
|
|
4332
|
+
if (!path.startsWith(`${dir2}/`)) continue;
|
|
4333
|
+
const read2 = [...dataReads.get(info.source) ?? []];
|
|
4334
|
+
if (read2.some((r) => r === path || stem(r) === stem(path)) || dataUnresolved.has(info.source) || dataTemplatePages.has(info.source)) out.push(prefix + info.out);
|
|
4335
|
+
}
|
|
4336
|
+
return out;
|
|
4337
|
+
},
|
|
4338
|
+
dataInTitles() {
|
|
4339
|
+
return dataInTitles;
|
|
4340
|
+
},
|
|
4341
|
+
invalidateData() {
|
|
4342
|
+
dataBy = /* @__PURE__ */ new Map();
|
|
4343
|
+
templatesBy = /* @__PURE__ */ new Map();
|
|
4344
|
+
dataDirHas = /* @__PURE__ */ new Map();
|
|
4148
4345
|
}
|
|
4149
4346
|
};
|
|
4150
4347
|
async function emitGenerated() {
|
|
@@ -4667,6 +4864,8 @@ function hreflangFor(cfg, unit, all, languages, slug) {
|
|
|
4667
4864
|
var CONFIG_FILES = /* @__PURE__ */ new Set(["dogsbay.config.yml", "dogsbay.config.yaml"]);
|
|
4668
4865
|
var NAV_FILES2 = /(^|\/)nav\.(ya?ml|json)$/;
|
|
4669
4866
|
var START_ONLY = /(^|\/)(_dogsbay\/search\/|llms(-full)?\.txt$)/;
|
|
4867
|
+
var SITE_WIDE = /^(_dogsbay\/assets\/|_dogsbay\/site\.js$|robots\.txt$|_headers$|_redirects$|sitemap-index\.xml$)/;
|
|
4868
|
+
var UNIT_CSS = /(^|\/)_dogsbay\/site\.css$/;
|
|
4670
4869
|
async function openDevSession(input, o = {}) {
|
|
4671
4870
|
let out = /* @__PURE__ */ new Map();
|
|
4672
4871
|
let units = /* @__PURE__ */ new Map();
|
|
@@ -4903,6 +5102,23 @@ async function openDevSession(input, o = {}) {
|
|
|
4903
5102
|
continue;
|
|
4904
5103
|
}
|
|
4905
5104
|
if (TOPIC_MAP.test(p)) continue;
|
|
5105
|
+
const dataDirs = (x) => [...new Set(x.unit.members.map((m) => dataDirOf(config, m.source)))].filter((d) => p.startsWith(`${d}/`));
|
|
5106
|
+
const dataUnits = [...units.values()].filter((x) => dataDirs(x).length);
|
|
5107
|
+
if (dataUnits.length) {
|
|
5108
|
+
for (const x of dataUnits) {
|
|
5109
|
+
if (x.handle.dataInTitles()) {
|
|
5110
|
+
lazy.add(x.unit.prefix);
|
|
5111
|
+
mark("unit");
|
|
5112
|
+
continue;
|
|
5113
|
+
}
|
|
5114
|
+
x.handle.invalidateData();
|
|
5115
|
+
for (const page of x.handle.dataReaders(p)) {
|
|
5116
|
+
stale.add(page);
|
|
5117
|
+
mark("page");
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
continue;
|
|
5121
|
+
}
|
|
4906
5122
|
const u = unitOf(p);
|
|
4907
5123
|
if (!u) continue;
|
|
4908
5124
|
const content = input.files.get(p);
|
|
@@ -4937,7 +5153,7 @@ async function openDevSession(input, o = {}) {
|
|
|
4937
5153
|
for (const prefix of lazy) {
|
|
4938
5154
|
const u = units.get(prefix);
|
|
4939
5155
|
for (const key of [...out.keys()]) {
|
|
4940
|
-
if (START_ONLY.test(key)) continue;
|
|
5156
|
+
if (START_ONLY.test(key) || SITE_WIDE.test(key) || UNIT_CSS.test(key)) continue;
|
|
4941
5157
|
if (unitForSitePath(key) === u) out.delete(key);
|
|
4942
5158
|
}
|
|
4943
5159
|
for (const s of [...stale]) if (unitForSitePath(s) === u) stale.delete(s);
|
|
@@ -23,6 +23,7 @@ import { type FileRef, type TextRef, type TextStream } from "./detach.js";
|
|
|
23
23
|
import { type TemplateBudget } from "./preprocess.js";
|
|
24
24
|
import { ParseCache } from "@dogsbay/minja/browser";
|
|
25
25
|
import { ScanCache } from "./scan-cache.js";
|
|
26
|
+
import { DataCache, type DataLimits } from "@dogsbay/format-dogsbay-md/browser";
|
|
26
27
|
import { type RedirectInput, type RedirectRule } from "./site-files.js";
|
|
27
28
|
import { DiagramStore, type DiagramRenderer, type DiagramRenderers } from "./diagrams.js";
|
|
28
29
|
export type FileContent = string | Uint8Array | FileRef | TextRef;
|
|
@@ -75,6 +76,14 @@ export interface BuildInput {
|
|
|
75
76
|
sink?: (path: string, content: SinkContent) => void | Promise<void>;
|
|
76
77
|
/** Characters of include fragments to keep in memory between pages (default 8M). */
|
|
77
78
|
textCacheChars?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Limits on the data files a build reads (docs/data-files.md in the dogsbay repo): per file, per
|
|
81
|
+
* build, values, depth, rows, columns, cell size. Default: the loader's (`DEFAULT_DATA_LIMITS`).
|
|
82
|
+
* An untrusted build never reads past `UNTRUSTED_DATA_LIMITS`, whatever is given here.
|
|
83
|
+
*/
|
|
84
|
+
dataLimits?: Partial<DataLimits>;
|
|
85
|
+
/** Bytes of parsed data files kept between pages and units (default 32 MB), weighed as the loader weighs them. */
|
|
86
|
+
dataCacheBytes?: number;
|
|
78
87
|
/** Called after each page, for progress reporting. */
|
|
79
88
|
onPage?: (done: number, total: number, unit: string) => void;
|
|
80
89
|
/**
|
|
@@ -141,11 +150,21 @@ export interface BuildInput {
|
|
|
141
150
|
/**
|
|
142
151
|
* Highlights code blocks off the main thread (the Node CLI passes a pool of
|
|
143
152
|
* worker threads). With it, pages are read and parsed a few ahead of their
|
|
144
|
-
* turn and their code blocks highlighted in the pool
|
|
145
|
-
*
|
|
146
|
-
*
|
|
153
|
+
* turn (`highlightAhead`) and their code blocks highlighted in the pool; each
|
|
154
|
+
* page renders from the blocks delivered for it, which then move into the
|
|
155
|
+
* highlight cache. Output is the same: a block the pool has not delivered is
|
|
156
|
+
* highlighted on the main thread as before.
|
|
147
157
|
*/
|
|
148
158
|
highlightPool?: HighlightPool;
|
|
159
|
+
/**
|
|
160
|
+
* How far ahead of rendering pages are prepared for the pool: at most `pages` pages (default 24; at least 1) and,
|
|
161
|
+
* beyond the next page, `chars` of their source plus the HTML delivered for them and not yet cached (default: no
|
|
162
|
+
* limit). Without a pool nothing is read ahead.
|
|
163
|
+
*/
|
|
164
|
+
highlightAhead?: {
|
|
165
|
+
pages?: number;
|
|
166
|
+
chars?: number;
|
|
167
|
+
};
|
|
149
168
|
/** Called once with the redirects the build writes (checked and resolved; site paths). */
|
|
150
169
|
onRedirects?: (rules: readonly RedirectRule[]) => void;
|
|
151
170
|
/**
|
|
@@ -161,9 +180,9 @@ export interface BuildInput {
|
|
|
161
180
|
export type SiteCssBuilder = (o: CssOptions) => Promise<string>;
|
|
162
181
|
/** The stylesheet builder of a build: the caller's, else Tailwind's. */
|
|
163
182
|
export declare function siteCssBuilder(input: BuildInput): Promise<SiteCssBuilder>;
|
|
164
|
-
/** Renders code blocks as `Highlighter.render` does, elsewhere; one result per request, in order. */
|
|
183
|
+
/** Renders code blocks as `Highlighter.render` does, elsewhere; one result per request, in order (null: not rendered there, so highlighted here). */
|
|
165
184
|
export interface HighlightPool {
|
|
166
|
-
render(blocks: CodeRequest[]): Promise<string[]>;
|
|
185
|
+
render(blocks: CodeRequest[]): Promise<(string | null)[]>;
|
|
167
186
|
}
|
|
168
187
|
/** A page as `BuildInput.onPageBuilt` sees it. */
|
|
169
188
|
export interface BuiltPage {
|
|
@@ -211,6 +230,8 @@ export interface SiteState {
|
|
|
211
230
|
parseCache: ParseCache | false;
|
|
212
231
|
/** Template time left for the build, or for a dev session's current request. */
|
|
213
232
|
templateBudget: TemplateBudget;
|
|
233
|
+
/** Parsed data files, by path and text (so an edited file is parsed again), shared by units and a dev session's rebuilds. */
|
|
234
|
+
dataCache: DataCache;
|
|
214
235
|
}
|
|
215
236
|
export interface BuildStats {
|
|
216
237
|
pages: number;
|
|
@@ -259,6 +280,9 @@ export interface BuildProblem {
|
|
|
259
280
|
/** The warning without its `[unit]` and file. */
|
|
260
281
|
message: string;
|
|
261
282
|
text: string;
|
|
283
|
+
/** For a data directive's problem in a data file: that file (a project path), and its 1-based line when known. */
|
|
284
|
+
dataFile?: string;
|
|
285
|
+
dataLine?: number;
|
|
262
286
|
}
|
|
263
287
|
/** Where a warning is, and its message when the text says more (a file first). */
|
|
264
288
|
type ProblemAt = Partial<Omit<BuildProblem, "code" | "text">>;
|
|
@@ -284,7 +308,7 @@ export interface PageDiagnostic {
|
|
|
284
308
|
* that one's lines; one only the templated text has has no `line`, and its message says the lines
|
|
285
309
|
* it names are the templated page's. Messages are detached (a session keeps them; some quote a line).
|
|
286
310
|
*/
|
|
287
|
-
export declare function linedDiagnostics(found: ParseDiagnostic[], raw: string, body: string, parsed: string): PageDiagnostic[];
|
|
311
|
+
export declare function linedDiagnostics(found: ParseDiagnostic[], raw: string, body: string, parsed: string, after?: string): PageDiagnostic[];
|
|
288
312
|
/** A file's text, read now if it is lazy. */
|
|
289
313
|
export declare function readText(c: FileContent): Promise<string>;
|
|
290
314
|
/**
|
|
@@ -319,6 +343,16 @@ export interface UnitHandle {
|
|
|
319
343
|
forget(path: string): void;
|
|
320
344
|
/** Whether a page of the unit has included this file (so its includers must render again). */
|
|
321
345
|
included(path: string): boolean;
|
|
346
|
+
/**
|
|
347
|
+
* The pages to render again when a data file (a project path) changes: those whose directives or
|
|
348
|
+
* template read it, those whose data directives were left in place, and template pages that use
|
|
349
|
+
* `data`. Empty when the unit does not read its folder.
|
|
350
|
+
*/
|
|
351
|
+
dataReaders(path: string): string[];
|
|
352
|
+
/** Whether a template page's title or description uses `data` (resolved in pass 1: a change needs the unit built again). */
|
|
353
|
+
dataInTitles(): boolean;
|
|
354
|
+
/** Forget what the unit loaded from data files: readers, template pages and their values, which folders had files. */
|
|
355
|
+
invalidateData(): void;
|
|
322
356
|
/** The project files the unit's nav was read from: `nav:` files, topic maps and every map they include. */
|
|
323
357
|
navInputs: string[];
|
|
324
358
|
}
|
|
@@ -43,6 +43,12 @@ export interface SourceDef {
|
|
|
43
43
|
* source's root, else its folders.
|
|
44
44
|
*/
|
|
45
45
|
nav?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The source's own data folder (`data:` on the source, docs/data-files.md in the dogsbay repo),
|
|
48
|
+
* project-relative: a versioned site whose versions need different data gives each its own.
|
|
49
|
+
* Unset: the site's (`SiteConfig.dataDir`).
|
|
50
|
+
*/
|
|
51
|
+
data?: string;
|
|
46
52
|
}
|
|
47
53
|
export type SearchEngine = "builtin" | "pagefind" | "both";
|
|
48
54
|
export interface SiteConfig {
|
|
@@ -139,6 +145,14 @@ export interface SiteConfig {
|
|
|
139
145
|
* default (BuildInput.defaultHtml), "keep" for local builds, "sanitize" in the Worker.
|
|
140
146
|
*/
|
|
141
147
|
html?: "keep" | "sanitize";
|
|
148
|
+
/**
|
|
149
|
+
* The data folder (content.data, shared with the Astro pipeline): YAML, JSON, CSV and TSV files
|
|
150
|
+
* pages read by name (`::data-table`, `::data-cards`, `data.<name>` in template pages).
|
|
151
|
+
* Project-relative; "data" (beside dogsbay.config.yml) unless set. `dataDirSet`: named in the
|
|
152
|
+
* config, so a folder with no files is reported.
|
|
153
|
+
*/
|
|
154
|
+
dataDir: string;
|
|
155
|
+
dataDirSet: boolean;
|
|
142
156
|
/** Click-to-zoom images, from `plugins: [@dogsbay/plugin-image-zoom]` with its options. */
|
|
143
157
|
imageZoom?: {
|
|
144
158
|
background: string;
|
|
@@ -278,9 +292,12 @@ export declare function projectPath(unit: BuildUnit, path: string): string;
|
|
|
278
292
|
export declare const TOPIC_MAP: RegExp;
|
|
279
293
|
/**
|
|
280
294
|
* The project folders and files a build reads: every source's root, the theme folder, extra
|
|
281
|
-
* stylesheets,
|
|
295
|
+
* stylesheets, the folder of each source's own nav file (a topic map's includes sit beside it), and
|
|
296
|
+
* the data folders (the site's, and any source's own; one that does not exist is not read).
|
|
282
297
|
* [""] when a source is the whole project. What the CLI reads from disk, and a dev session watches.
|
|
283
298
|
*/
|
|
284
299
|
export declare function projectRoots(cfg: SiteConfig): string[];
|
|
300
|
+
/** The data folder a source's pages read: its own `data:`, else the site's. */
|
|
301
|
+
export declare function dataDirOf(cfg: SiteConfig, s: SourceDef): string;
|
|
285
302
|
/** A source's content root as a path prefix: "" for ".", else "content/". */
|
|
286
303
|
export declare function sourceRoot(s: SourceDef): string;
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* its pages (`rebuildUnitLazy`), its old output is dropped, and all its
|
|
11
11
|
* pages are stale. What pass 1 read from each unchanged page is kept
|
|
12
12
|
* between rebuilds (src/core/scan-cache.ts), so this is cheap;
|
|
13
|
+
* - a data file (in the site's data folder or a source's own): the pages
|
|
14
|
+
* that read it (or a file of the same name), have data directives left
|
|
15
|
+
* in place, or are template pages of that folder are stale; a unit whose
|
|
16
|
+
* template titles use `data` is built again;
|
|
13
17
|
* - a topic map, a map it includes or a source's `nav:` file: the units
|
|
14
18
|
* that read it are built again, as above;
|
|
15
19
|
* - an asset: its entry is replaced;
|
|
@@ -14,7 +14,7 @@ export type DiagramResult = {
|
|
|
14
14
|
};
|
|
15
15
|
export interface DiagramRenderer {
|
|
16
16
|
render(batch: DiagramRequest[]): Promise<DiagramResult[]>;
|
|
17
|
-
/** Calls allowed per build
|
|
17
|
+
/** Calls allowed per build. Default: no limit (the Worker's renderer, on one session, has none). */
|
|
18
18
|
maxCalls?: number;
|
|
19
19
|
/** Most diagrams per call; larger batches are split (each part is a call). Default: no limit. */
|
|
20
20
|
maxBatch?: number;
|
|
@@ -55,10 +55,10 @@ export declare function fencedDiagrams(src: string): string[];
|
|
|
55
55
|
*
|
|
56
56
|
* The SVGs the current unit's built pages use are pinned until `endUnit()`:
|
|
57
57
|
* evicting one would cost a renderer call to get it back (the first deployed
|
|
58
|
-
* test spent 20 of
|
|
59
|
-
* `maxPinnedChars`, so memory stays bounded however many
|
|
60
|
-
* has; beyond it, SVGs are evicted and pages render them
|
|
61
|
-
*
|
|
58
|
+
* test, when each call was a Worker call, spent 20 of its 30 that way).
|
|
59
|
+
* Pinning stops at `maxPinnedChars`, so memory stays bounded however many
|
|
60
|
+
* diagrams a unit has; beyond it, SVGs are evicted and pages render them
|
|
61
|
+
* again, within `maxCalls` if the renderer has one. Unpinned SVGs are kept up to `maxChars` for later
|
|
62
62
|
* units, least recently used first out.
|
|
63
63
|
*/
|
|
64
64
|
export declare class DiagramStore {
|
|
@@ -14,6 +14,16 @@ export interface PreprocessResult {
|
|
|
14
14
|
error?: string;
|
|
15
15
|
/** Not rendered: the build's template time budget was spent (counted in `TemplateBudget.skipped`). */
|
|
16
16
|
skipped?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Tags content mode does not know (`{% for %}`, `{% macro %}`), kept as written: minja's warnings
|
|
19
|
+
* (outside code, which is masked). `line` is in `text` when `include` is unset.
|
|
20
|
+
*/
|
|
21
|
+
warnings?: {
|
|
22
|
+
tag: string;
|
|
23
|
+
line: number;
|
|
24
|
+
include?: string;
|
|
25
|
+
variable?: string;
|
|
26
|
+
}[];
|
|
17
27
|
}
|
|
18
28
|
/**
|
|
19
29
|
* Template time for a whole build (plans/worker-scale.md stage 3): each render gets the shorter of
|
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.139",
|
|
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",
|
|
@@ -33,8 +33,10 @@
|
|
|
33
33
|
"test": "npm run build:client && vitest run",
|
|
34
34
|
"test:browser": "playwright test",
|
|
35
35
|
"check": "npm run build:client && tsc --noEmit && tsc --noEmit -p src/worker/tsconfig.json",
|
|
36
|
-
"dev:worker": "npm run build:client && wrangler dev -c wrangler.jsonc -c wrangler.diagrams.jsonc --port 8798",
|
|
36
|
+
"dev:worker": "npm run build:client && wrangler dev -c wrangler.jsonc -c wrangler.diagrams.jsonc -c wrangler.highlight.jsonc --port 8798",
|
|
37
37
|
"deploy": "npm run build:client && wrangler deploy",
|
|
38
|
+
"deploy:diagrams": "npm run build:client && wrangler deploy -c wrangler.diagrams.jsonc",
|
|
39
|
+
"deploy:highlight": "npm run build:client && wrangler deploy -c wrangler.highlight.jsonc",
|
|
38
40
|
"test:worker": "npm run build:client && node scripts/test-worker.mjs",
|
|
39
41
|
"build:package": "npm run build:client && node scripts/build-package.mjs",
|
|
40
42
|
"prepublishOnly": "npm run build:package"
|
|
@@ -47,38 +49,38 @@
|
|
|
47
49
|
"tailwindcss": "^4.3.2"
|
|
48
50
|
},
|
|
49
51
|
"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.
|
|
52
|
+
"@dogsbay/elements": "0.2.0-beta.139",
|
|
53
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.139",
|
|
54
|
+
"@dogsbay/format-openapi": "0.2.0-beta.139",
|
|
55
|
+
"@dogsbay/format-topicmap": "0.2.0-beta.139",
|
|
56
|
+
"@dogsbay/highlight": "0.2.0-beta.139",
|
|
57
|
+
"@dogsbay/minja": "0.2.0-beta.139",
|
|
58
|
+
"@dogsbay/primitives": "0.2.0-beta.139",
|
|
59
|
+
"@dogsbay/render-html": "0.2.0-beta.139",
|
|
60
|
+
"@dogsbay/search": "0.2.0-beta.139",
|
|
61
|
+
"@dogsbay/serialize-core": "0.2.0-beta.139",
|
|
62
|
+
"@dogsbay/site-check": "0.2.0-beta.139",
|
|
63
|
+
"@dogsbay/site-files": "0.2.0-beta.139",
|
|
64
|
+
"@dogsbay/types": "0.2.0-beta.139"
|
|
63
65
|
},
|
|
64
66
|
"devDependencies": {
|
|
65
67
|
"@axe-core/playwright": "^4.12.1",
|
|
66
68
|
"@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.
|
|
69
|
+
"@dogsbay/elements": "0.2.0-beta.139",
|
|
70
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.139",
|
|
71
|
+
"@dogsbay/format-openapi": "0.2.0-beta.139",
|
|
72
|
+
"@dogsbay/format-topicmap": "0.2.0-beta.139",
|
|
73
|
+
"@dogsbay/highlight": "0.2.0-beta.139",
|
|
74
|
+
"@dogsbay/icons": "0.2.0-beta.139",
|
|
75
|
+
"@dogsbay/minja": "0.2.0-beta.139",
|
|
76
|
+
"@dogsbay/primitives": "0.2.0-beta.139",
|
|
77
|
+
"@dogsbay/render-html": "0.2.0-beta.139",
|
|
78
|
+
"@dogsbay/search": "0.2.0-beta.139",
|
|
79
|
+
"@dogsbay/serialize-core": "0.2.0-beta.139",
|
|
80
|
+
"@dogsbay/site-check": "0.2.0-beta.139",
|
|
81
|
+
"@dogsbay/site-files": "0.2.0-beta.139",
|
|
82
|
+
"@dogsbay/types": "0.2.0-beta.139",
|
|
83
|
+
"@dogsbay/ui": "0.2.0-beta.139",
|
|
82
84
|
"@mermanjs/web": "0.7.0",
|
|
83
85
|
"@playwright/test": "1.61.1",
|
|
84
86
|
"@types/node": "^22.10.0",
|