@dogsbay/ssg 0.2.0-beta.143 → 0.2.0-beta.146

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.
@@ -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.143 (MIT)
4
+ @dogsbay/elements@0.2.0-beta.146 (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.143 (MIT)
10
+ @dogsbay/primitives@0.2.0-beta.146 (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.143 (MIT)
16
+ @dogsbay/render-html@0.2.0-beta.146 (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.143 (MIT)
22
+ @dogsbay/search@0.2.0-beta.146 (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.143 (MIT)
28
+ @dogsbay/ui@0.2.0-beta.146 (MIT)
29
29
  https://github.com/dogsbay/dogsbay.git
30
30
 
31
31
  Licensed under MIT; the package ships no licence file.
@@ -18,7 +18,8 @@ import {
18
18
 
19
19
  // src/core/build.ts
20
20
  import { dogsbayMdToTree, splitFrontmatter as splitFrontmatter2, treeToDogsbayMd } from "@dogsbay/format-dogsbay-md/browser";
21
- import { buildHreflangLinks, isMountRoot, missingTranslations, mountRefs, mountRootNote, namespaceNavGroup, placeMountedNav, prefixSlug as prefixSlug2, rewriteContentRefs } from "@dogsbay/site-files";
21
+ import { mergeHeaders, mergeRedirects, productRootRedirects, siteFiles, siteRootTarget, toScript, unitOf } from "@dogsbay/site-files";
22
+ import { buildHreflangLinks, isMountRoot, missingTranslations, mountRefs, mountRootNote, namespaceNavGroup, namespacePrefixes, placeMountedNav, prefixSlug as prefixSlug2, rewriteContentRefs } from "@dogsbay/site-files";
22
23
 
23
24
  // src/core/canonical.ts
24
25
  import { decideCanonical } from "@dogsbay/site-files";
@@ -26,7 +27,7 @@ import { declaresParams, splitFrontmatter } from "@dogsbay/format-dogsbay-md/bro
26
27
 
27
28
  // src/core/config.ts
28
29
  import { scriptUrl } from "@dogsbay/render-html";
29
- import { assertLeafSitemap, checkVersionSeo, isNamespaceAxisActive, namespaceSegments, parseMount } from "@dogsbay/site-files";
30
+ import { assertLeafSitemap, checkVersionSeo, isNamespaceAxisActive, namespaceSegments, parseMount, ProductConfigError, productModel, productOf, sourceNamespace, sourceUnit, unitSegments } from "@dogsbay/site-files";
30
31
 
31
32
  // src/core/yaml.ts
32
33
  import { load } from "js-yaml";
@@ -36,6 +37,17 @@ function loadYaml(text) {
36
37
  }
37
38
 
38
39
  // src/core/config.ts
40
+ function shared(f) {
41
+ try {
42
+ return f();
43
+ } catch (e) {
44
+ if (e instanceof ProductConfigError) throw new ConfigError(e.message);
45
+ throw e;
46
+ }
47
+ }
48
+ function versionsOf(cfg, product) {
49
+ return cfg.productVersions?.get(product ?? "") ?? { versions: cfg.versions, defaultVersion: cfg.defaultVersion };
50
+ }
39
51
  var ConfigError = class extends Error {
40
52
  };
41
53
  function sourceTemplates(v, path) {
@@ -67,10 +79,10 @@ function unitId(kind, v) {
67
79
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(id)) throw new ConfigError(`${kind}: id "${id.slice(0, 80)}" must be one URL segment of letters, digits, dots, dashes and underscores`);
68
80
  return id;
69
81
  }
70
- function sourceMount(s) {
82
+ function sourceMount(s, products) {
71
83
  const at = `content.sources[${String(s.path)}]`;
72
84
  if (s.from !== "openapi") throw new ConfigError(`${at}: mount is only for a "from: openapi" source`);
73
- if (s.name != null) throw new ConfigError(`${at} has both name and mount: a mounted source goes inside its version, so it takes no name`);
85
+ if (s.name != null && !products) throw new ConfigError(`${at} has both name and mount: a mounted source goes inside its version, so it takes no name (with content.products:, it names its product)`);
74
86
  const parsed = parseMount(s.mount);
75
87
  if ("error" in parsed) throw new ConfigError(`${at}: ${parsed.error}`);
76
88
  return parsed.segments.join("/");
@@ -140,6 +152,7 @@ function parseConfig(text, overrides = {}) {
140
152
  if (navMode !== void 0 && navMode !== "client" && navMode !== "ssr-full") throw new ConfigError(`build.navMode must be "client" or "ssr-full"; got ${JSON.stringify(navMode)}`);
141
153
  const site = raw.site ?? {};
142
154
  const content = raw.content ?? {};
155
+ const productsBlock = content.products != null;
143
156
  const ssg = raw.ssg ?? {};
144
157
  const agent = raw.agent ?? {};
145
158
  const signal = agent.contentSignal ?? {};
@@ -147,7 +160,7 @@ function parseConfig(text, overrides = {}) {
147
160
  path: sourcePath(s),
148
161
  ...s.name != null ? { name: sourceName(s.name) } : {},
149
162
  ...s.from != null ? { from: sourceFrom(s.from, String(s.path)) } : {},
150
- ...s.mount != null ? { mount: sourceMount(s) } : {},
163
+ ...s.mount != null ? { mount: sourceMount(s, productsBlock) } : {},
151
164
  version: s.version != null ? unitId("version", s.version) : void 0,
152
165
  locale: s.locale != null ? unitId("locale", s.locale) : void 0,
153
166
  attributes: s.attributes ?? void 0,
@@ -164,7 +177,7 @@ function parseConfig(text, overrides = {}) {
164
177
  hidden: v.hidden || void 0,
165
178
  removed: removedOf(v.removed, String(v.id)),
166
179
  ...versionSeoOf(v, String(v.id)),
167
- ...versionPathOf(v, String(v.id))
180
+ ...versionPathOf(v, String(v.id), productsBlock)
168
181
  }));
169
182
  const locales = (content.locales ?? raw.locales ?? []).map((l) => ({
170
183
  id: unitId("locales", l.id),
@@ -267,6 +280,7 @@ function parseConfig(text, overrides = {}) {
267
280
  }
268
281
  if (dv.noindex) throw new ConfigError(`versions: "${dv.id}" is the defaultVersion and has noindex: true; older versions' pages name it as their canonical`);
269
282
  }
283
+ if (productsBlock) Object.assign(cfg, productsOf(content, raw, cfg));
270
284
  return cfg;
271
285
  }
272
286
  var TAG_COLORS = /* @__PURE__ */ new Set(["blue", "amber", "emerald", "violet", "rose", "slate"]);
@@ -392,11 +406,41 @@ function versionSeoOf(v, id) {
392
406
  ...typeof v.sitemap === "boolean" ? { sitemap: v.sitemap } : {}
393
407
  };
394
408
  }
395
- function versionPathOf(v, id) {
409
+ function versionPathOf(v, id, products = false) {
396
410
  if (v.path === void 0 || v.path === null) return {};
397
- if (v.path !== "") throw new ConfigError(`versions: "${id}".path can only be "" (serve the version at the root)`);
411
+ if (products && v.path !== "") return { path: String(v.path) };
412
+ if (v.path !== "") throw new ConfigError(`versions: "${id}".path can only be "" (serve the version at the root); another segment needs content.products:`);
398
413
  return { path: "" };
399
414
  }
415
+ function productsOf(content, raw, cfg) {
416
+ const siteVersions = content.versions ?? raw.versions;
417
+ const written = (Array.isArray(content.sources) ? content.sources : []).filter((s) => typeof s?.path === "string");
418
+ const sources = written.length ? written : [{ path: "content" }];
419
+ const model = shared(() => productModel({ products: content.products, sources, versions: siteVersions, defaultVersion: content.defaultVersion ?? raw.defaultVersion, locales: cfg.locales, defaultLocale: cfg.defaultLocale }));
420
+ if (!model) return {};
421
+ const productVersions = /* @__PURE__ */ new Map();
422
+ for (const p of model.products) {
423
+ const rawList = (p.name ? content.products[p.name]?.versions : void 0) ?? siteVersions ?? [];
424
+ const versions = p.versions.map((v) => {
425
+ const r = rawList.find((x) => String(x?.id) === v.id) ?? {};
426
+ return {
427
+ id: v.id,
428
+ label: v.label,
429
+ ...v.eol ? { eol: true } : {},
430
+ ...v.prerelease ? { prerelease: true } : {},
431
+ ...v.hidden ? { hidden: true } : {},
432
+ ...r.removed != null ? { removed: removedOf(r.removed, v.id) } : {},
433
+ ...v.canonical ? { canonical: v.canonical } : {},
434
+ ...v.noindex !== void 0 ? { noindex: v.noindex } : {},
435
+ ...v.sitemap !== void 0 ? { sitemap: v.sitemap } : {},
436
+ ...v.path !== v.id ? { path: v.path } : {},
437
+ ...v.banner ? { banner: v.banner } : {}
438
+ };
439
+ });
440
+ productVersions.set(p.name ?? "", { versions, ...p.defaultVersion ? { defaultVersion: p.defaultVersion } : {} });
441
+ }
442
+ return { products: model, productVersions };
443
+ }
400
444
  function unitOfPath(units, sitePath2) {
401
445
  let best;
402
446
  for (const u of units) {
@@ -406,7 +450,12 @@ function unitOfPath(units, sitePath2) {
406
450
  }
407
451
  return best;
408
452
  }
453
+ function groupPrefix(u) {
454
+ return [u.product, u.locale].filter(Boolean).join("/");
455
+ }
456
+ var sameProduct = (a, b) => (a.product ?? "") === (b.product ?? "");
409
457
  function buildUnits(cfg) {
458
+ if (cfg.products) return productUnits(cfg, cfg.products);
410
459
  const withDefaults2 = cfg.sources.map((s) => ({
411
460
  source: s,
412
461
  version: s.version ?? cfg.defaultVersion,
@@ -424,26 +473,26 @@ function buildUnits(cfg) {
424
473
  }
425
474
  }
426
475
  const namespaces = isNamespaceAxisActive(cfg.sources.filter((s) => !s.mount));
427
- if (namespaces && (versionAxis || localeAxis)) {
428
- throw new ConfigError("named sources (content.sources name:) cannot be combined with versions or locales yet (plans/openapi-sources.md)");
429
- }
476
+ const products = namespaces && (versionAxis || localeAxis);
430
477
  for (const s of cfg.sources) {
431
478
  if (s.from === "openapi" && cfg.sources.length > 1 && !s.name && !s.mount) throw new ConfigError(`content.sources[${s.path}]: an OpenAPI source beside other sources needs a name: or a mount:, so its pages do not land on theirs`);
432
479
  }
433
480
  const units = /* @__PURE__ */ new Map();
434
481
  for (const u of withDefaults2) {
435
482
  const atRoot = cfg.versions.find((v) => v.id === u.version)?.path === "";
436
- const prefix = [localeAxis ? u.locale : void 0, versionAxis && !atRoot ? u.version : void 0].filter(Boolean).join("/");
437
- const member = u.source.mount ? { source: u.source, namespace: "", mount: u.source.mount } : { source: u.source, namespace: namespaceSegments(u.source, cfg.sources).join("/") };
483
+ const product = products && !u.source.mount ? sourceNamespace(u.source) : void 0;
484
+ const prefix = [product, localeAxis ? u.locale : void 0, versionAxis && !atRoot ? u.version : void 0].filter(Boolean).join("/");
485
+ const member = u.source.mount ? { source: u.source, namespace: "", mount: u.source.mount } : { source: u.source, namespace: products ? "" : namespaceSegments(u.source, cfg.sources).join("/") };
438
486
  const unit = units.get(prefix);
439
487
  if (!unit) {
440
- units.set(prefix, { source: u.source, members: [member], version: versionAxis ? u.version : void 0, locale: localeAxis ? u.locale : void 0, prefix });
488
+ units.set(prefix, { source: u.source, members: [member], version: versionAxis ? u.version : void 0, ...versionAxis && u.version ? { versionPath: atRoot ? "" : u.version } : {}, locale: localeAxis ? u.locale : void 0, ...product ? { product } : {}, prefix });
441
489
  continue;
442
490
  }
443
491
  if (member.mount) {
444
492
  const taken = unit.members.find((m) => m.mount === member.mount);
445
493
  if (taken) throw new ConfigError(`two sources are mounted at "${member.mount}" in the same version and locale (${taken.source.path}, ${u.source.path})`);
446
494
  } else if (unit.members.some((m) => !m.mount)) {
495
+ if (products) throw new ConfigError(`two sources publish to "${prefix || "/"}" (${unit.members.find((m) => !m.mount).source.path}, ${u.source.path}): give each its own name, version or locale`);
447
496
  if (!namespaces) throw new ConfigError(`two sources publish to the same prefix "${prefix || "/"}"`);
448
497
  if (unit.members.some((m) => !m.mount && m.namespace === member.namespace)) {
449
498
  throw new ConfigError(member.namespace ? `two sources are named "${member.namespace}"` : `two unnamed sources publish to the same prefix "${prefix || "/"}"`);
@@ -453,6 +502,16 @@ function buildUnits(cfg) {
453
502
  const main = unit.members.find((m) => m.source === unit.source);
454
503
  if (!member.namespace && !member.mount && (main.namespace || main.mount)) unit.source = u.source;
455
504
  }
505
+ if (products) {
506
+ const names = new Set([...units.values()].map((u) => u.product).filter(Boolean));
507
+ for (const unit of units.values()) {
508
+ const main = unit.members.find((m) => !m.mount);
509
+ for (const m of unit.members.filter((x) => x.mount)) {
510
+ if (!main) throw new ConfigError(`content.sources[${m.source.path}]: a mounted source goes beside the unnamed source of its version and locale, and there is none at "${unit.prefix || "/"}"; a mount inside a named source's version is not supported yet`);
511
+ if (names.has(m.mount.split("/")[0])) throw new ConfigError(`content.sources[${m.source.path}]: mount "${m.mount}" starts with the name of a source, where that source's pages are; mount it elsewhere`);
512
+ }
513
+ }
514
+ }
456
515
  for (const unit of units.values()) {
457
516
  for (const m of unit.members) {
458
517
  const ns = m.mount && unit.members.find((o) => o.namespace && m.mount.split("/")[0] === o.namespace);
@@ -461,6 +520,43 @@ function buildUnits(cfg) {
461
520
  }
462
521
  return [...units.values()];
463
522
  }
523
+ function productUnits(cfg, model) {
524
+ const units = /* @__PURE__ */ new Map();
525
+ for (const s of cfg.sources) {
526
+ const key = shared(() => sourceUnit(model, s, cfg.defaultLocale));
527
+ const prefix = shared(() => unitSegments(model, key)).join("/");
528
+ const member = s.mount ? { source: s, namespace: "", mount: s.mount } : { source: s, namespace: "" };
529
+ const unit = units.get(prefix);
530
+ if (!unit) {
531
+ const versionPath = key.version === void 0 ? void 0 : shared(() => productOf(model, key.product)).versions.find((v) => v.id === key.version)?.path;
532
+ units.set(prefix, {
533
+ source: s,
534
+ members: [member],
535
+ ...key.version !== void 0 ? { version: key.version, versionPath } : {},
536
+ ...key.locale !== void 0 ? { locale: key.locale } : {},
537
+ ...key.product ? { product: key.product } : {},
538
+ prefix
539
+ });
540
+ continue;
541
+ }
542
+ if (member.mount) {
543
+ const taken = unit.members.find((m) => m.mount === member.mount);
544
+ if (taken) throw new ConfigError(`two sources are mounted at "${member.mount}" in the same version and locale (${taken.source.path}, ${s.path})`);
545
+ } else if (unit.members.some((m) => !m.mount)) {
546
+ throw new ConfigError(`two sources publish to "${prefix || "/"}" (${unit.members.find((m) => !m.mount).source.path}, ${s.path}): give each its own name, version or locale`);
547
+ }
548
+ unit.members.push(member);
549
+ if (!member.mount) unit.source = s;
550
+ }
551
+ const names = new Set(model.products.map((p) => p.name).filter(Boolean));
552
+ for (const unit of units.values()) {
553
+ if (unit.prefix !== "") continue;
554
+ for (const m of unit.members) {
555
+ if (m.mount && names.has(m.mount.split("/")[0])) throw new ConfigError(`content.sources[${m.source.path}]: mount "${m.mount}" starts with the name of a product, where that product's pages are; mount it elsewhere`);
556
+ }
557
+ }
558
+ return [...units.values()];
559
+ }
464
560
  function memberPath(unit, projectPath2) {
465
561
  let best;
466
562
  for (const m of unit.members) {
@@ -1069,16 +1165,19 @@ function parseFrontMatter(text) {
1069
1165
  return {};
1070
1166
  }
1071
1167
  }
1168
+ var tableKey = (u) => u.product ? `${u.product}\0${u.locale ?? ""}` : u.locale ?? "";
1072
1169
  async function defaultTables(cfg, units, built, files, read, warn = () => {
1073
1170
  }, fm) {
1074
1171
  const tables = /* @__PURE__ */ new Map();
1075
- if (!cfg.defaultVersion || !built.some((u) => u.version && u.version !== cfg.defaultVersion)) return tables;
1076
- for (const unit of units.filter((u) => u.version === cfg.defaultVersion)) {
1172
+ const dv = (u) => versionsOf(cfg, u.product).defaultVersion;
1173
+ const older = (u) => !!u.version && !!dv(u) && u.version !== dv(u);
1174
+ if (!built.some(older)) return tables;
1175
+ for (const unit of units.filter((u) => u.version !== void 0 && u.version === dv(u))) {
1077
1176
  const t = await scan(cfg, unit, units, files, fm ?? frontMatterCache(read), read);
1078
- if (!t.pages.size && built.some((u) => u.version !== cfg.defaultVersion && u.locale === unit.locale)) {
1079
- warn(`[canonical] the default version "${cfg.defaultVersion}" has no pages under ${unit.source.path}, so older versions' pages keep themselves as canonical`);
1177
+ if (!t.pages.size && built.some((u) => older(u) && u.locale === unit.locale && sameProduct(u, unit))) {
1178
+ warn(`[canonical] the default version "${dv(unit)}" has no pages under ${unit.source.path}, so older versions' pages keep themselves as canonical`);
1080
1179
  }
1081
- tables.set(unit.locale ?? "", t);
1180
+ tables.set(tableKey(unit), t);
1082
1181
  }
1083
1182
  return tables;
1084
1183
  }
@@ -1163,11 +1262,12 @@ async function scan(cfg, unit, units, files, frontMatterOf, read, mode = { kind:
1163
1262
  return { prefix: unit.prefix, pages, redirects };
1164
1263
  }
1165
1264
  function pageCanonical(cfg, unit, tables, page, urls) {
1166
- const table = tables.get(unit.locale ?? "");
1167
- const version = cfg.versions.find((v) => v.id === unit.version);
1265
+ const table = tables.get(tableKey(unit));
1266
+ const { versions, defaultVersion } = versionsOf(cfg, unit.product);
1267
+ const version = versions.find((v) => v.id === unit.version);
1168
1268
  const fmCanonical = typeof page.frontmatter.canonicalUrl === "string" ? page.frontmatter.canonicalUrl : void 0;
1169
1269
  const d = decideCanonical({
1170
- isDefault: !unit.version || unit.version === cfg.defaultVersion || !table,
1270
+ isDefault: !unit.version || unit.version === defaultVersion || !table,
1171
1271
  path: page.slug || "index",
1172
1272
  version,
1173
1273
  frontmatter: fmCanonical,
@@ -1312,31 +1412,31 @@ var CLIENT_CANDIDATES = ['""', '"&"', '"\\\\$&")).join("|")})', '"\\\\$&")}"', '
1312
1412
  var SITE_NOTICES = `Third-party software in this site's _dogsbay/ files (dogsbay.js, site.css) and pages
1313
1413
 
1314
1414
  ========================================================================
1315
- @dogsbay/elements@0.2.0-beta.143 (MIT)
1415
+ @dogsbay/elements@0.2.0-beta.146 (MIT)
1316
1416
  https://github.com/dogsbay/dogsbay.git
1317
1417
 
1318
1418
  Licensed under MIT; the package ships no licence file.
1319
1419
 
1320
1420
  ========================================================================
1321
- @dogsbay/primitives@0.2.0-beta.143 (MIT)
1421
+ @dogsbay/primitives@0.2.0-beta.146 (MIT)
1322
1422
  https://github.com/dogsbay/dogsbay.git
1323
1423
 
1324
1424
  Licensed under MIT; the package ships no licence file.
1325
1425
 
1326
1426
  ========================================================================
1327
- @dogsbay/render-html@0.2.0-beta.143 (MIT)
1427
+ @dogsbay/render-html@0.2.0-beta.146 (MIT)
1328
1428
  https://github.com/dogsbay/dogsbay.git
1329
1429
 
1330
1430
  Licensed under MIT; the package ships no licence file.
1331
1431
 
1332
1432
  ========================================================================
1333
- @dogsbay/search@0.2.0-beta.143 (MIT)
1433
+ @dogsbay/search@0.2.0-beta.146 (MIT)
1334
1434
  https://github.com/dogsbay/dogsbay.git
1335
1435
 
1336
1436
  Licensed under MIT; the package ships no licence file.
1337
1437
 
1338
1438
  ========================================================================
1339
- @dogsbay/ui@0.2.0-beta.143 (MIT)
1439
+ @dogsbay/ui@0.2.0-beta.146 (MIT)
1340
1440
  https://github.com/dogsbay/dogsbay.git
1341
1441
 
1342
1442
  Licensed under MIT; the package ships no licence file.
@@ -2498,6 +2598,8 @@ var EN = {
2498
2598
  prerelease: "pre-release",
2499
2599
  latest: "latest",
2500
2600
  oldVersion: "You are viewing documentation for an older version.",
2601
+ eolVersion: "This version is no longer maintained.",
2602
+ prereleaseVersion: "This version is not released yet.",
2501
2603
  goLatest: "Go to the latest version",
2502
2604
  notFound: "Page not found",
2503
2605
  notFoundBody: "The page you asked for does not exist in this version of the documentation.",
@@ -3064,7 +3166,8 @@ function diagramStore(input, cfg) {
3064
3166
  async function siteCssBuilder(input) {
3065
3167
  return input.css ?? (await import("./chunks/tailwind-fallback-KPRFHW3C.mjs")).buildSiteCss;
3066
3168
  }
3067
- var SITE_FILES = /* @__PURE__ */ new Set(["robots.txt", "_headers", "_redirects", "sitemap-index.xml"]);
3169
+ var SITE_FILES = /* @__PURE__ */ new Set(["robots.txt", "sitemap-index.xml"]);
3170
+ var MERGED_FILES = /* @__PURE__ */ new Set(["_headers", "_redirects"]);
3068
3171
  function reporter(warnings, problems) {
3069
3172
  return (code, text, at = {}) => {
3070
3173
  const t = detach(text);
@@ -3079,7 +3182,8 @@ function linedDiagnostics(found, raw, body, parsed, after = "of the page after t
3079
3182
  const LINE = /\bline (\d+)/g;
3080
3183
  const shifted = (m) => m.replace(LINE, (_, n) => `line ${Number(n) + offset}`);
3081
3184
  const named = (d) => d.name === void 0 ? {} : { name: detach(d.name) };
3082
- const lined = (d) => ({ code: d.code, message: detach(shifted(d.message)), line: d.line + offset, ...named(d) });
3185
+ const columns = (d) => ({ ...d.column === void 0 ? {} : { column: d.column }, ...d.endColumn === void 0 ? {} : { endColumn: d.endColumn } });
3186
+ const lined = (d) => ({ code: d.code, message: detach(shifted(d.message)), line: d.line + offset, ...named(d), ...columns(d) });
3083
3187
  if (parsed === body) return found.map(lined);
3084
3188
  let asWritten;
3085
3189
  try {
@@ -3094,6 +3198,36 @@ function linedDiagnostics(found, raw, body, parsed, after = "of the page after t
3094
3198
  return { code: d.code, message: detach(d.message.replace(LINE, `line $1 ${after}`)), ...named(d) };
3095
3199
  });
3096
3200
  }
3201
+ function sourceLinesOf(tree, raw, body, parsed) {
3202
+ const offset = raw.slice(0, raw.length - body.length).split("\n").length - 1;
3203
+ let map = (line) => line + offset;
3204
+ if (parsed !== body) {
3205
+ const a = body.split("\n");
3206
+ const b = parsed.split("\n");
3207
+ if (a.length !== b.length || /\{[%#]/.test(body)) {
3208
+ let head = 0;
3209
+ while (head < a.length && head < b.length && a[head] === b[head]) head++;
3210
+ let tail = 0;
3211
+ while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail]) tail++;
3212
+ map = (line) => line <= head ? line + offset : line > b.length - tail ? line - (b.length - a.length) + offset : void 0;
3213
+ }
3214
+ }
3215
+ const walk = (node) => {
3216
+ if (Array.isArray(node)) {
3217
+ for (const n of node) walk(n);
3218
+ return;
3219
+ }
3220
+ if (!node || typeof node !== "object") return;
3221
+ const meta = node.meta;
3222
+ if (meta && typeof meta.line === "number") {
3223
+ const to = map(meta.line);
3224
+ if (to === void 0) delete meta.line;
3225
+ else meta.line = to;
3226
+ }
3227
+ for (const v of Object.values(node)) if (v && typeof v === "object" && v !== meta) walk(v);
3228
+ };
3229
+ walk(tree);
3230
+ }
3097
3231
  var decoder = new TextDecoder();
3098
3232
  var DATA_DIRECTIVE_NODES = new Set(DATA_DIRECTIVES);
3099
3233
  var DEFAULT_DATA_CACHE_BYTES = 32 * 1024 * 1024;
@@ -3167,7 +3301,7 @@ function frontMatterCopy(fm) {
3167
3301
  }
3168
3302
  }
3169
3303
  function rebuildUnitLazy(ctx, unit) {
3170
- return buildUnit(unit, ctx.allUnits, ctx.cfg, ctx.input, ctx.highlighter, ctx.emit, ctx.report, ctx.phase, ctx.theme.templates, ctx.state, { lazy: true, diagnostics: ctx.diagnostics });
3304
+ return buildUnit(unit, ctx.allUnits, ctx.cfg, ctx.input, ctx.highlighter, ctx.emit, ctx.report, ctx.phase, ctx.theme.templates, ctx.state, { lazy: true, diagnostics: ctx.diagnostics, dataLines: ctx.dataLines });
3171
3305
  }
3172
3306
  async function build(input, hooks = {}) {
3173
3307
  const t0 = now();
@@ -3179,6 +3313,7 @@ async function build(input, hooks = {}) {
3179
3313
  const allUnits = buildUnits(cfg);
3180
3314
  const units = selectUnits(allUnits, input.units);
3181
3315
  checkRootVersion(allUnits, input.files);
3316
+ checkProductFolders(allUnits, input.files);
3182
3317
  const out = /* @__PURE__ */ new Map();
3183
3318
  const sink = input.sink;
3184
3319
  let classes = new Set(CLIENT_CANDIDATES);
@@ -3207,7 +3342,7 @@ async function build(input, hooks = {}) {
3207
3342
  phase.init = now() - t0;
3208
3343
  let pages = 0;
3209
3344
  let assets = 0;
3210
- const state = { redirects: [], notes: /* @__PURE__ */ new Map(), 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) };
3345
+ const state = { redirects: [], notes: /* @__PURE__ */ new Map(), navCache: navCache(), written: /* @__PURE__ */ new Set(), stubs: [], pageOut: /* @__PURE__ */ new Map(), sitemaps: /* @__PURE__ */ new Map(), supplied: /* @__PURE__ */ new Set(), hostFiles: /* @__PURE__ */ new Map(), 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) };
3211
3346
  for (const r of cfg.redirects) {
3212
3347
  const from = sitePath(r.from, cfg.basePath);
3213
3348
  if (!from) {
@@ -3228,10 +3363,10 @@ async function build(input, hooks = {}) {
3228
3363
  if (siteBase(cfg)) state.languages = await languageTables(cfg, allUnits, units, input.files, readText, frontMatterOf);
3229
3364
  const translations = await translationTables(cfg, allUnits, input.files, readText, input.includeDrafts === true, frontMatterOf);
3230
3365
  frontMatterOf = void 0;
3231
- hooks.context?.({ cfg, allUnits, input, highlighter, emit, warnings, problems, report, phase, theme, state, diagnostics: hooks.diagnostics });
3366
+ hooks.context?.({ cfg, allUnits, input, highlighter, emit, warnings, problems, report, phase, theme, state, diagnostics: hooks.diagnostics, dataLines: hooks.dataLines });
3232
3367
  for (const unit of units) {
3233
3368
  classes = new Set(CLIENT_CANDIDATES);
3234
- const r = await buildUnit(unit, allUnits, cfg, input, highlighter, emit, report, phase, theme.templates, state, { keep: hooks.keep, lazy: hooks.lazy, diagnostics: hooks.diagnostics });
3369
+ const r = await buildUnit(unit, allUnits, cfg, input, highlighter, emit, report, phase, theme.templates, state, { keep: hooks.keep, lazy: hooks.lazy, diagnostics: hooks.diagnostics, dataLines: hooks.dataLines });
3235
3370
  hooks.unitBuilt?.(unit, r, classes);
3236
3371
  pages += r.pages;
3237
3372
  assets += r.assets;
@@ -3261,20 +3396,36 @@ async function build(input, hooks = {}) {
3261
3396
  defaultLocale: cfg.defaultLocale,
3262
3397
  versions: cfg.versions,
3263
3398
  locales: cfg.locales,
3264
- units: allUnits.map((u) => ({ prefix: u.prefix, version: u.version, locale: u.locale }))
3399
+ units: allUnits.map((u) => ({ prefix: u.prefix, version: u.version, locale: u.locale, ...u.product ? { product: u.product } : {}, ...cfg.products && u.versionPath !== u.version ? { versionPath: u.versionPath } : {} })),
3400
+ // With content.products, each product's own versions and default (key "" for the unnamed source's).
3401
+ ...cfg.productVersions ? { products: Object.fromEntries(cfg.productVersions) } : {}
3265
3402
  // As nav.js: a host may inline the script, and a name with "<\/script>" must not close it.
3266
3403
  }).replace(/<\//g, "<\\/")};
3267
3404
  `
3268
3405
  );
3406
+ if (cfg.products) await emit("_dogsbay/versions.js", `${toScript("__DOGSBAY_VERSIONS__", siteFiles(cfg.products, []).versions)}
3407
+ `);
3269
3408
  const def = defaultUnit(allUnits, cfg);
3270
3409
  const tSite = now();
3410
+ const rootUnit = allUnits.find((u) => u.prefix === "");
3411
+ if (rootUnit && !input.untrusted) {
3412
+ for (const n of MERGED_FILES) {
3413
+ const own = input.files.get(sourceRoot(rootUnit.source) + n);
3414
+ if (own !== void 0) state.hostFiles.set(n, own);
3415
+ }
3416
+ }
3271
3417
  await emitSiteFiles(cfg, allUnits, units, def, state, emit, report, input.onRedirects);
3272
3418
  phase.site = (phase.site ?? 0) + now() - tSite;
3273
3419
  if (def && def.prefix) {
3274
3420
  await emit("index.html", redirectPage(`${def.prefix}/index.html`, cfg.name));
3275
3421
  await emit("404.html", redirectPage(`${def.prefix}/404.html`, cfg.name));
3422
+ }
3423
+ if (cfg.products) {
3424
+ for (const [from, to] of productRootRedirects(cfg.products)) await emit(`${from.slice(1)}index.html`, redirectPage(`${relativeHome(from, to)}index.html`, cfg.name));
3425
+ } else if (def && def.prefix) {
3276
3426
  for (const u of allUnits) {
3277
- if (u.locale && u.version && u.version === cfg.defaultVersion && u.prefix !== u.locale) await emit(`${u.locale}/index.html`, redirectPage(`${u.version}/index.html`, cfg.name));
3427
+ const root = groupPrefix(u);
3428
+ if (root && u.version && u.version === cfg.defaultVersion && u.prefix !== root) await emit(`${root}/index.html`, redirectPage(`${u.version}/index.html`, cfg.name));
3278
3429
  }
3279
3430
  }
3280
3431
  state.stubs = await writeTranslationStubs(cfg, allUnits, units, translations, state, emit);
@@ -3284,14 +3435,17 @@ async function build(input, hooks = {}) {
3284
3435
  files: out,
3285
3436
  warnings,
3286
3437
  problems,
3287
- notes: [...state.notes.values()].flat(),
3438
+ notes: [...state.notes.values()].flat().map((n) => n.message),
3288
3439
  config: cfg,
3289
3440
  stats: { pages, assets, units: units.length, ms: Math.round(now() - t0), msByPhase: roundAll(phase), highlightCache: highlighter.cacheStats(), ...state.parseCache ? { parseCache: state.parseCache.stats() } : {} }
3290
3441
  };
3291
3442
  }
3292
3443
  function selectUnits(allUnits, names) {
3293
3444
  if (!names) return allUnits;
3294
- const named = (u, name) => u.prefix === name || u.version !== void 0 && u.version === name && u.prefix === (u.locale ?? "");
3445
+ const named = (u, name) => {
3446
+ if (u.product) return u.product === name || u.prefix === name && u.prefix !== groupPrefix(u) || u.version !== void 0 && name === `${groupPrefix(u)}/${u.version}`;
3447
+ return u.prefix === name || u.version !== void 0 && (u.version === name && u.prefix === groupPrefix(u) || [groupPrefix(u), u.version].filter(Boolean).join("/") === name);
3448
+ };
3295
3449
  const unknown = names.filter((p) => !allUnits.some((u) => named(u, p)));
3296
3450
  if (unknown.length) {
3297
3451
  const list = allUnits.map((u) => u.prefix ? `"${u.prefix}"` : `"${u.version ?? ""}" (at the root)`).join(", ");
@@ -3301,13 +3455,13 @@ function selectUnits(allUnits, names) {
3301
3455
  }
3302
3456
  function checkRootVersion(allUnits, files) {
3303
3457
  const rootOf = (x) => x.source.path === "." ? "" : x.source.path.replace(/^\.\//, "").replace(/\/+$/, "") + "/";
3304
- for (const u of allUnits.filter((x) => x.version && x.prefix === (x.locale ?? ""))) {
3458
+ for (const u of allUnits.filter((x) => x.version && x.prefix === groupPrefix(x))) {
3305
3459
  const root = rootOf(u);
3306
3460
  const nested = allUnits.find((x) => x !== u && rootOf(x).startsWith(root) && rootOf(x) !== root);
3307
3461
  if (nested) {
3308
3462
  throw new ConfigError(`source "${nested.source.path}" is inside "${u.source.path}", the source of version "${u.version}" served at the root; give each version its own folder`);
3309
3463
  }
3310
- const segs = new Set(allUnits.filter((x) => x !== u && x.locale === u.locale && x.version).map((x) => x.version));
3464
+ const segs = new Set(allUnits.filter((x) => x !== u && x.locale === u.locale && sameProduct(x, u) && x.versionPath).map((x) => x.versionPath));
3311
3465
  if (!segs.size) continue;
3312
3466
  for (const k of files.keys()) {
3313
3467
  if (!k.startsWith(root)) continue;
@@ -3315,7 +3469,26 @@ function checkRootVersion(allUnits, files) {
3315
3469
  if (rel.split("/").some((s) => s.startsWith("_") || s.startsWith("."))) continue;
3316
3470
  const first = rel.split("/")[0].replace(/\.(md|markdown)$/i, "");
3317
3471
  if (segs.has(first) && (rel.includes("/") || /\.(md|markdown)$/i.test(rel))) {
3318
- throw new ConfigError(`${k} is at ${[u.prefix, first].filter(Boolean).join("/")}/\u2026, where version "${first}" is served (version "${u.version}" is at the root); rename the folder or the page`);
3472
+ const other = allUnits.find((x) => x.versionPath === first && x.locale === u.locale && sameProduct(x, u));
3473
+ throw new ConfigError(`${k} is at ${[u.prefix, first].filter(Boolean).join("/")}/\u2026, where version "${other.version}" is served (version "${u.version}" is at the root); rename the folder or the page`);
3474
+ }
3475
+ }
3476
+ }
3477
+ }
3478
+ function checkProductFolders(allUnits, files) {
3479
+ const names = new Set(allUnits.map((u) => u.product).filter((p) => !!p));
3480
+ if (!names.size) return;
3481
+ for (const u of allUnits.filter((x) => !x.product && x.prefix === "")) {
3482
+ const roots = u.members.filter((m) => !m.mount && m.source.from !== "openapi").map((m) => sourceRoot(m.source));
3483
+ const others = allUnits.filter((x) => x !== u).flatMap((x) => x.members.map((m) => sourceRoot(m.source))).filter(Boolean);
3484
+ for (const k of files.keys()) {
3485
+ const root = roots.find((r) => k.startsWith(r));
3486
+ if (root === void 0 || others.some((o) => k.startsWith(o) && o.length > root.length)) continue;
3487
+ const rel = k.slice(root.length);
3488
+ if (rel.split("/").some((seg) => seg.startsWith("_") || seg.startsWith("."))) continue;
3489
+ const first = rel.split("/")[0].replace(/\.(md|markdown)$/i, "");
3490
+ if (names.has(first) && (rel.includes("/") || /\.(md|markdown)$/i.test(rel))) {
3491
+ throw new ConfigError(`${k} is at ${first}/\u2026, where the source named "${first}" is published; rename the folder or the page`);
3319
3492
  }
3320
3493
  }
3321
3494
  }
@@ -3326,15 +3499,23 @@ async function emitSiteFiles(cfg, all, built, def, state, emit, report, onRedire
3326
3499
  if (state.supplied.has(p)) report("site", `[site] ${p} comes from the content, so the generated one is not written`);
3327
3500
  else await emit(p, content);
3328
3501
  };
3502
+ const cloudflare = cfg.deployTarget === "cloudflare-workers";
3503
+ const merged = async (p, generated, merge) => {
3504
+ const own = state.hostFiles.get(p);
3505
+ if (own === void 0 && generated === void 0) return;
3506
+ const { text, notes } = merge(own === void 0 ? "" : await readText(own), generated, cloudflare);
3507
+ for (const n of notes) report("site", `[site] ${n}`);
3508
+ await emit(p, text);
3509
+ };
3329
3510
  await put("robots.txt", buildRobotsTxt(cfg, defPrefix));
3330
- if (cfg.llmsTxt) await put("_headers", buildHeadersFile(cfg, defPrefix));
3511
+ await merged("_headers", cfg.llmsTxt ? buildHeadersFile(cfg, defPrefix) : void 0, mergeHeaders);
3331
3512
  if (siteBase(cfg)) {
3332
3513
  const maps = all.flatMap((u) => state.sitemaps.get(u.prefix) ?? [[u.prefix, "sitemap-0.xml"].filter(Boolean).join("/")]);
3333
3514
  await put("sitemap-index.xml", buildSitemapIndex(cfg, maps));
3334
3515
  } else if (cfg.url) {
3335
3516
  report("config", `[site] site.url "${cfg.url}" has no http:// or https://, so canonical links, the sitemap and absolute llms.txt links are left out`);
3336
3517
  }
3337
- if (!state.redirects.length) return;
3518
+ if (!state.redirects.length) return merged("_redirects", void 0, mergeRedirects);
3338
3519
  const known = (p) => {
3339
3520
  const u = unitOfPath(all, p);
3340
3521
  return !!u && built.includes(u);
@@ -3344,9 +3525,15 @@ async function emitSiteFiles(cfg, all, built, def, state, emit, report, onRedire
3344
3525
  for (const m of w) report("redirects", `[redirects] ${m}`);
3345
3526
  onRedirects?.(rules);
3346
3527
  for (const r of rules) await emit(stubFile(r), renderRedirectPage(r, cfg));
3347
- if (cfg.deployTarget === "cloudflare-workers" && rules.length) await put("_redirects", renderRedirectsFile(rules, cfg));
3528
+ const ownRedirects = state.hostFiles.has("_redirects");
3529
+ await merged("_redirects", (cloudflare || ownRedirects) && rules.length ? renderRedirectsFile(rules, cfg) : void 0, mergeRedirects);
3348
3530
  }
3349
3531
  function defaultUnit(units, cfg) {
3532
+ if (cfg.products) {
3533
+ const target = siteRootTarget(cfg.products);
3534
+ if (target) return units.find((u) => `/${u.prefix}/` === target || u.prefix === "" && target === "/") ?? units[0];
3535
+ units = units.filter((u) => !u.product);
3536
+ }
3350
3537
  return units.find((u) => (u.version ?? cfg.defaultVersion) === cfg.defaultVersion && (u.locale ?? cfg.defaultLocale) === cfg.defaultLocale) ?? units[0];
3351
3538
  }
3352
3539
  var mathLoad;
@@ -3394,7 +3581,7 @@ async function buildUnit(unit, allUnits, cfg, input, highlighter, emit, report,
3394
3581
  };
3395
3582
  };
3396
3583
  const namespaceOf = (p) => named.find((m) => p === m.namespace || p.startsWith(m.namespace + "/"))?.namespace ?? "";
3397
- const globalPrefixes = new Set(cfg.taxonomies.map((t2) => t2.path.split("/")[0]).filter(Boolean));
3584
+ const globalPrefixes = /* @__PURE__ */ new Set([...cfg.taxonomies.map((t2) => t2.path.split("/")[0]).filter(Boolean), ...namespacePrefixes(cfg.sources.filter((s) => !s.mount))]);
3398
3585
  const mounts = [cfg.basePath, ...cfg.linkPrefixes ?? []].filter(Boolean);
3399
3586
  const prefix = unit.prefix ? unit.prefix + "/" : "";
3400
3587
  const label = unit.prefix || "/";
@@ -3695,7 +3882,8 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
3695
3882
  if (mount) {
3696
3883
  const there = infos.get(slug);
3697
3884
  if (there && isMountRoot(page.slug) && !there.virtual) {
3698
- notes.push(mountRootNote(m.mount, prefixSlug2(unit.prefix ? unit.prefix.split("/") : [], slug)));
3885
+ const page2 = prefixSlug2(unit.prefix ? unit.prefix.split("/") : [], slug);
3886
+ notes.push({ message: mountRootNote(m.mount, page2), file: srcOf(there), page: page2 });
3699
3887
  label2 = there.title;
3700
3888
  continue;
3701
3889
  }
@@ -3964,8 +4152,9 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
3964
4152
  t2 = now();
3965
4153
  let tree;
3966
4154
  try {
3967
- const parsed = dogsbayMdToTree(pre.text, mode.diagnostics ? { diagnostics: true } : void 0);
4155
+ const parsed = dogsbayMdToTree(pre.text, mode.diagnostics || mode.dataLines ? { ...mode.diagnostics ? { diagnostics: true } : {}, ...mode.dataLines ? { sourceLines: true } : {} } : void 0);
3968
4156
  tree = parsed.tree;
4157
+ if (mode.dataLines) sourceLinesOf(tree, raw, bodySrc, pre.text);
3969
4158
  if (mode.diagnostics) (diagnostics ??= []).push(...linedDiagnostics(parsed.diagnostics ?? [], raw, bodySrc, pre.text, templated === bodySrc ? "of the page with its data directives drawn" : void 0));
3970
4159
  } catch (e) {
3971
4160
  warn2("parse", `parse error: ${e.message}`);
@@ -4146,7 +4335,8 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
4146
4335
  imageZoom: cfg.imageZoom ? cfg.imageZoom.excludeInsideLinks ? "outside-links" : "all" : void 0,
4147
4336
  imageCaptions: cfg.imageCaptions,
4148
4337
  rawHtml: sanitizing ? (h) => sanitizeHtml(h, noteRemoved) : void 0,
4149
- embeddedEndpoints: apiLayout.wide ? apiLayout.embeddedEndpoints : void 0
4338
+ embeddedEndpoints: apiLayout.wide ? apiLayout.embeddedEndpoints : void 0,
4339
+ dataLines: mode.dataLines
4150
4340
  });
4151
4341
  let hasH1 = tree[0]?.type === "heading" && Number(tree[0].props?.level) === 1;
4152
4342
  const post = postNeighbours.has(info.slug) ? blog.get(info.slug) : void 0;
@@ -4317,6 +4507,8 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
4317
4507
  t = now();
4318
4508
  for (const a of assetPaths) {
4319
4509
  if (SITE_FILES.has(prefix + a)) state.supplied.add(prefix + a);
4510
+ if (MERGED_FILES.has(prefix + a)) continue;
4511
+ if (MERGED_FILES.has(a)) warn("site", `${a} is copied to ${prefix}${a}, where hosts do not read it: they read _headers and _redirects at the site root only, from the unit served there`, { file: abs(a) });
4320
4512
  await emit(prefix + a, files.get(a));
4321
4513
  }
4322
4514
  const pagesJs = `(window.__DOGSBAY_PAGES__=window.__DOGSBAY_PAGES__||{})[${JSON.stringify(unit.prefix)}]=${JSON.stringify([...[...infos.values()].filter((i) => i.slug !== replaced).map((i) => i.out), ...generatedOuts])};
@@ -4829,13 +5021,14 @@ function switchOptions(kind, unit, all, cfg, out, siteRoot, t) {
4829
5021
  const axis = kind === "version" ? "version" : "locale";
4830
5022
  const other = kind === "version" ? "locale" : "version";
4831
5023
  if (!unit[axis]) return [];
4832
- const peers = all.filter((u) => u[other] === unit[other] && u[axis]);
4833
- const defs = kind === "version" ? cfg.versions : cfg.locales;
5024
+ const peers = all.filter((u) => u[other] === unit[other] && sameProduct(u, unit) && u[axis]);
5025
+ const { versions, defaultVersion } = versionsOf(cfg, unit.product);
5026
+ const defs = kind === "version" ? versions : cfg.locales;
4834
5027
  const ordered = defs.length ? defs.map((d) => peers.find((u) => u[axis] === d.id)).filter((u) => !!u) : peers;
4835
- return ordered.filter((u) => !(kind === "version" && cfg.versions.find((v) => v.id === u.version)?.hidden && u !== unit)).map((u) => {
4836
- const v = kind === "version" ? cfg.versions.find((d) => d.id === u.version) : void 0;
5028
+ return ordered.filter((u) => !(kind === "version" && versions.find((v) => v.id === u.version)?.hidden && u !== unit)).map((u) => {
5029
+ const v = kind === "version" ? versions.find((d) => d.id === u.version) : void 0;
4837
5030
  const l = kind === "locale" ? cfg.locales.find((d) => d.id === u.locale) : void 0;
4838
- const note = v?.eol ? t("eol") : v?.prerelease ? t("prerelease") : v && v.id === cfg.defaultVersion && v.id !== "latest" ? t("latest") : void 0;
5031
+ const note = v?.eol ? t("eol") : v?.prerelease ? t("prerelease") : v && v.id === defaultVersion && v.id !== "latest" ? t("latest") : void 0;
4839
5032
  return {
4840
5033
  label: v?.label ?? l?.label ?? String(u[axis]),
4841
5034
  href: `${siteRoot}${u.prefix ? u.prefix + "/" : ""}${out}`,
@@ -4846,19 +5039,28 @@ function switchOptions(kind, unit, all, cfg, out, siteRoot, t) {
4846
5039
  });
4847
5040
  }
4848
5041
  function versionBanner(unit, all, cfg, out, siteRoot, t) {
5042
+ const link = (latest2) => ` <a href="${esc3(`${siteRoot}${latest2.prefix ? latest2.prefix + "/" : ""}${out}`)}" data-db-version-link data-prefix="${esc3(latest2.prefix)}">${esc3(t("goLatest"))}</a>`;
5043
+ if (cfg.products) {
5044
+ if (unit.version === void 0 || versionsOf(cfg, unit.product).versions.find((d) => d.id === unit.version)?.hidden) return void 0;
5045
+ const b = shared(() => unitOf(cfg.products, { product: unit.product, locale: unit.locale, version: unit.version })).banner;
5046
+ if (!b) return void 0;
5047
+ const text = esc3(b.kind === "custom" ? b.text : t(b.kind === "eol" ? "eolVersion" : b.kind === "prerelease" ? "prereleaseVersion" : "oldVersion"));
5048
+ const latest2 = b.latest && all.find((u) => `/${u.prefix ? u.prefix + "/" : ""}` === b.latest.home);
5049
+ return latest2 ? text + link(latest2) : text;
5050
+ }
4849
5051
  if (!unit.version || !cfg.defaultVersion || unit.version === cfg.defaultVersion) return void 0;
4850
5052
  const v = cfg.versions.find((d) => d.id === unit.version);
4851
5053
  if (v?.hidden) return void 0;
4852
- const latest = all.find((u) => u.version === cfg.defaultVersion && u.locale === unit.locale);
5054
+ const latest = all.find((u) => u.version === cfg.defaultVersion && u.locale === unit.locale && sameProduct(u, unit));
4853
5055
  if (!latest) return void 0;
4854
- return `${esc3(t("oldVersion"))} <a href="${esc3(`${siteRoot}${latest.prefix ? latest.prefix + "/" : ""}${out}`)}" data-db-version-link data-prefix="${esc3(latest.prefix)}">${esc3(t("goLatest"))}</a>`;
5056
+ return esc3(t("oldVersion")) + link(latest);
4855
5057
  }
4856
5058
  async function writeTranslationStubs(cfg, all, built, tables, state, emit, exists = () => false) {
4857
5059
  const def = cfg.defaultLocale;
4858
5060
  if (!def || !tables.size) return [];
4859
- const unitOf = (locale, version) => all.find((u) => u.locale === locale && u.version === version);
5061
+ const unitOf2 = (locale, version) => all.find((u) => u.locale === locale && u.version === version && !u.product);
4860
5062
  const missing = missingTranslations(
4861
- all.filter((u) => u.locale).map((u) => {
5063
+ all.filter((u) => u.locale && !u.product).map((u) => {
4862
5064
  const t = tables.get(u.prefix);
4863
5065
  return { locale: u.locale, version: u.version, pages: !t ? [] : u.locale === def ? t.pages.keys() : [...t.pages.keys(), ...t.redirects.keys()] };
4864
5066
  }),
@@ -4869,9 +5071,9 @@ async function writeTranslationStubs(cfg, all, built, tables, state, emit, exist
4869
5071
  const written = [];
4870
5072
  for (const m of missing) {
4871
5073
  if (m.page === "404") continue;
4872
- const home = unitOf(def, m.version);
5074
+ const home = unitOf2(def, m.version);
4873
5075
  const out = tables.get(home.prefix).pages.get(m.page);
4874
- const target = unitOf(m.locale, m.version);
5076
+ const target = unitOf2(m.locale, m.version);
4875
5077
  if (!built.includes(target ?? home)) continue;
4876
5078
  const to = join(home.prefix, out);
4877
5079
  if (built.includes(home) && !pageFiles.has(to)) continue;
@@ -4882,6 +5084,10 @@ async function writeTranslationStubs(cfg, all, built, tables, state, emit, exist
4882
5084
  }
4883
5085
  return written;
4884
5086
  }
5087
+ function relativeHome(from, to) {
5088
+ if (to.startsWith(from)) return to.slice(from.length);
5089
+ return "../".repeat(from.split("/").filter(Boolean).length) + to.slice(1);
5090
+ }
4885
5091
  function redirectPage(target, name) {
4886
5092
  const u = esc3(target);
4887
5093
  return `<!doctype html><html><head><meta charset="utf-8"><title>${esc3(name)}</title><meta name="robots" content="noindex"><meta http-equiv="refresh" content="0; url=${u}"><link rel="canonical" href="${u}"><link rel="icon" href="data:,"></head><body><p><a href="${u}">${esc3(name)}</a></p></body></html>
@@ -4957,7 +5163,7 @@ function hreflangFor(cfg, unit, all, languages, slug) {
4957
5163
  const path = slug || "index";
4958
5164
  const alts = [];
4959
5165
  for (const u of all) {
4960
- if (!u.locale || u.version !== unit.version) continue;
5166
+ if (!u.locale || u.version !== unit.version || !sameProduct(u, unit)) continue;
4961
5167
  const out = languages.get(u.prefix)?.get(path);
4962
5168
  const url = out ? absoluteUrl(cfg, u.prefix, servedPath(out, cfg.linkStyle)) : void 0;
4963
5169
  if (url) alts.push({ lang: u.locale, url });
@@ -4969,7 +5175,7 @@ function hreflangFor(cfg, unit, all, languages, slug) {
4969
5175
  var CONFIG_FILES = /* @__PURE__ */ new Set(["dogsbay.config.yml", "dogsbay.config.yaml"]);
4970
5176
  var NAV_FILES2 = /(^|\/)nav\.(ya?ml|json)$/;
4971
5177
  var START_ONLY = /(^|\/)(_dogsbay\/search\/|llms(-full)?\.txt$)/;
4972
- var SITE_WIDE = /^(_dogsbay\/assets\/|_dogsbay\/site\.js$|robots\.txt$|_headers$|_redirects$|sitemap-index\.xml$)/;
5178
+ var SITE_WIDE = /^(_dogsbay\/assets\/|_dogsbay\/(site|versions)\.js$|robots\.txt$|_headers$|_redirects$|sitemap-index\.xml$)/;
4973
5179
  var UNIT_CSS = /(^|\/)_dogsbay\/site\.css$/;
4974
5180
  async function openDevSession(input, o = {}) {
4975
5181
  let out = /* @__PURE__ */ new Map();
@@ -5014,7 +5220,7 @@ async function openDevSession(input, o = {}) {
5014
5220
  } catch (e) {
5015
5221
  throw inConfig(e, name);
5016
5222
  }
5017
- const nextConfig = { ...parsed, llmsTxt: o.llms === true && parsed.llmsTxt, search: o.search === false ? false : parsed.search };
5223
+ const nextConfig = { ...parsed, llmsTxt: o.llms === true && parsed.llmsTxt, search: o.search === false ? false : parsed.search, math: o.math === false ? false : parsed.math };
5018
5224
  try {
5019
5225
  await build(
5020
5226
  // Drafts are shown while writing, as `dogsbay site dev` shows them.
@@ -5027,6 +5233,7 @@ async function openDevSession(input, o = {}) {
5027
5233
  watchScan(c, nextScan);
5028
5234
  },
5029
5235
  diagnostics: (f, list) => nextDiagnosed.set(f, list),
5236
+ dataLines: o.dataLines,
5030
5237
  // A copy: the build keeps adding the classes of pages it renders later to its own set.
5031
5238
  unitBuilt: (unit, handle, classes) => nextUnits.set(unit.prefix, { unit, handle, classes: new Set(classes) })
5032
5239
  }
@@ -5046,7 +5253,8 @@ async function openDevSession(input, o = {}) {
5046
5253
  stale = /* @__PURE__ */ new Set();
5047
5254
  if (o.lazy) for (const u of units.values()) for (const p of u.handle.pageOuts) stale.add(p);
5048
5255
  };
5049
- const unitOf = (path) => {
5256
+ const noteItems = () => [...ctx?.state.notes.values() ?? []].flat().map((n) => ({ ...n }));
5257
+ const unitOf2 = (path) => {
5050
5258
  let best;
5051
5259
  let bestLen = -1;
5052
5260
  for (const u of units.values()) {
@@ -5131,7 +5339,7 @@ async function openDevSession(input, o = {}) {
5131
5339
  const lazy = await textOf(c);
5132
5340
  return lazy ?? c;
5133
5341
  };
5134
- const pageFor = (source) => unitOf(source)?.handle.pageForSource(source);
5342
+ const pageFor = (source) => unitOf2(source)?.handle.pageForSource(source);
5135
5343
  return {
5136
5344
  get config() {
5137
5345
  return config;
@@ -5147,8 +5355,9 @@ async function openDevSession(input, o = {}) {
5147
5355
  return unitForSitePath(path)?.handle.sourceForPage(path);
5148
5356
  },
5149
5357
  notes() {
5150
- return [...ctx?.state.notes.values() ?? []].flat();
5358
+ return noteItems().map((n) => n.message);
5151
5359
  },
5360
+ noteItems,
5152
5361
  takeWarnings() {
5153
5362
  const all = ctx?.warnings ?? [];
5154
5363
  const fresh = all.slice(taken);
@@ -5241,7 +5450,7 @@ async function openDevSession(input, o = {}) {
5241
5450
  if (CONFIG_FILES.has(p)) continue;
5242
5451
  const readers = [...units.values()].filter((u2) => u2.handle.navInputs.includes(p) || u2.unit.members.some((m) => m.source.nav === p));
5243
5452
  if (readers.length) {
5244
- for (const u2 of [...readers, unitOf(p)]) if (u2) lazy.add(u2.unit.prefix);
5453
+ for (const u2 of [...readers, unitOf2(p)]) if (u2) lazy.add(u2.unit.prefix);
5245
5454
  mark("unit");
5246
5455
  continue;
5247
5456
  }
@@ -5263,7 +5472,7 @@ async function openDevSession(input, o = {}) {
5263
5472
  }
5264
5473
  continue;
5265
5474
  }
5266
- const u = unitOf(p);
5475
+ const u = unitOf2(p);
5267
5476
  if (!u) continue;
5268
5477
  const content = input.files.get(p);
5269
5478
  if (/\.(md|markdown)$/i.test(p)) {
@@ -5289,6 +5498,7 @@ async function openDevSession(input, o = {}) {
5289
5498
  const at = memberPath(u.unit, p)?.path ?? p.slice(sourceRoot(u.unit.source).length);
5290
5499
  const key = u.unit.prefix ? `${u.unit.prefix}/${at}` : at;
5291
5500
  const sanitizing = (config.html ?? input.defaultHtml ?? "keep") === "sanitize";
5501
+ if (key === "_headers" || key === "_redirects") continue;
5292
5502
  if (copiedPath(at) && await assetVerdict(at, content, input.untrusted === true, sanitizing) === "copy") out.set(key, content);
5293
5503
  else out.delete(key);
5294
5504
  }
@@ -5315,10 +5525,12 @@ async function openDevSession(input, o = {}) {
5315
5525
  units.set(prefix, next);
5316
5526
  for (const o2 of handle.pageOuts) markStale(o2, next, true);
5317
5527
  }
5318
- if (config.defaultVersion && [...lazy].some((prefix) => units.get(prefix)?.unit.version === config.defaultVersion)) {
5528
+ const isDefault = (u) => u.version !== void 0 && u.version === versionsOf(config, u.product).defaultVersion;
5529
+ const changedDefaults = [...lazy].map((prefix) => units.get(prefix)?.unit).filter((u) => !!u && isDefault(u));
5530
+ if (changedDefaults.length) {
5319
5531
  const all = [...units.values()].map((u) => u.unit);
5320
5532
  ctx.state.canonical = await defaultTables(config, all, all, input.files, readText);
5321
- for (const u of units.values()) if (u.unit.version !== config.defaultVersion) for (const o2 of u.handle.pageOuts) markStale(o2, u);
5533
+ for (const u of units.values()) if (u.unit.version !== void 0 && !isDefault(u.unit) && changedDefaults.some((d) => (d.product ?? "") === (u.unit.product ?? ""))) for (const o2 of u.handle.pageOuts) markStale(o2, u);
5322
5534
  }
5323
5535
  if (lazy.size && ctx.state.languages) {
5324
5536
  const all = [...units.values()].map((u) => u.unit);
@@ -201,19 +201,29 @@ export interface BuiltPage {
201
201
  /** The parsed body, after templating. */
202
202
  tree: TreeNode[];
203
203
  }
204
+ /** A note (BuildResult.notes) as data: its text, and the page it is about when there is one. */
205
+ export interface BuildNote {
206
+ message: string;
207
+ /** The page's source file, a project path (the content page standing as a mounted API's root). */
208
+ file?: string;
209
+ /** The page's path in the site (`3.32/reference/rest-api`), as the message names it. */
210
+ page?: string;
211
+ }
204
212
  /** What the site-level files need from every unit. Small: a few strings per page. */
205
213
  export interface SiteState {
206
214
  redirects: RedirectInput[];
207
215
  /** What the build says about the content, not a problem, by unit prefix (replaced when a unit is built again). */
208
- notes: Map<string, string[]>;
216
+ notes: Map<string, BuildNote[]>;
209
217
  /** The sidebar items kept between pages (src/theme/regions.ts). */
210
218
  navCache: NavCache;
211
219
  /** Site path ("en/latest/guide/x") → output file relative to the site root. */
212
220
  pageOut: Map<string, string>;
213
221
  /** Sitemap files, relative to the site root, by unit prefix. */
214
222
  sitemaps: Map<string, string[]>;
215
- /** Site-level files a content root supplies itself (robots.txt, _headers…); they win over generated ones. */
223
+ /** Site-level files a content root supplies itself (robots.txt, the sitemap index); they win over generated ones. */
216
224
  supplied: Set<string>;
225
+ /** The root's own `_headers` and `_redirects`, merged with the generated ones (@dogsbay/site-files host-files.ts). */
226
+ hostFiles: Map<string, FileContent>;
217
227
  /** A page rendered math, so the KaTeX stylesheet and fonts are written. */
218
228
  usedMath: boolean;
219
229
  /** Rendered diagrams, shared by the units of the build. */
@@ -309,6 +319,9 @@ export interface PageDiagnostic {
309
319
  line?: number;
310
320
  /** The directive's name, without colons, on `unknown-container` and `unknown-inline`. */
311
321
  name?: string;
322
+ /** The part of `line` meant, when the parser knows it: 1-based UTF-16 code units, `endColumn` exclusive. Only with `line`. */
323
+ column?: number;
324
+ endColumn?: number;
312
325
  }
313
326
  /**
314
327
  * The parser's diagnostics on `parsed` (the body after templating) with lines in the file: `line`,
@@ -318,6 +331,15 @@ export interface PageDiagnostic {
318
331
  * it names are the templated page's. Messages are detached (a session keeps them; some quote a line).
319
332
  */
320
333
  export declare function linedDiagnostics(found: ParseDiagnostic[], raw: string, body: string, parsed: string, after?: string): PageDiagnostic[];
334
+ /**
335
+ * A page's block lines (`meta.line`, from the parser's `sourceLines`) as lines of its file: `parsed`
336
+ * is the body after templating, `body` as written, `raw` the file. The front matter moves every line
337
+ * by its length. Templating that keeps the line count (variables, one-line includes) keeps each
338
+ * line where it was. One that changes it (an include, a condition) keeps the lines before and after
339
+ * the change, and a block in between loses its line: a wrong line would scroll the editor elsewhere.
340
+ * Changes the tree in place.
341
+ */
342
+ export declare function sourceLinesOf(tree: unknown, raw: string, body: string, parsed: string): void;
321
343
  /** A file's text, read now if it is lazy. */
322
344
  export declare function readText(c: FileContent): Promise<string>;
323
345
  /**
@@ -379,6 +401,7 @@ export interface BuildContext {
379
401
  theme: LoadedTheme;
380
402
  state: SiteState;
381
403
  diagnostics?: BuildHooks["diagnostics"];
404
+ dataLines?: boolean;
382
405
  }
383
406
  /** Internal: a dev session's view of a build (see src/core/dev.ts). */
384
407
  export interface BuildHooks {
@@ -392,15 +415,20 @@ export interface BuildHooks {
392
415
  * path, and the list (empty when it has none, so a fixed page clears).
393
416
  */
394
417
  diagnostics?: (file: string, list: PageDiagnostic[]) => void;
418
+ /**
419
+ * Mark each block with its line in the page's file (`data-line`, render-html's `dataLines`), for
420
+ * an editor's scroll sync. Lines templating moved are left out (`sourceLinesOf`).
421
+ */
422
+ dataLines?: boolean;
395
423
  unitBuilt?: (unit: BuildUnit, handle: UnitHandle, classes: Set<string>) => void;
396
424
  }
397
425
  /** Build one unit again without rendering its pages (they are rendered on request). */
398
426
  export declare function rebuildUnitLazy(ctx: BuildContext, unit: BuildUnit): Promise<UnitHandle>;
399
427
  export declare function build(input: BuildInput, hooks?: BuildHooks): Promise<BuildResult>;
400
- /** robots.txt, _headers, the sitemap index and redirects: the files that belong to the whole site. */
401
428
  /**
402
- * The units `--unit` names: by prefix, and a version served at the root
403
- * (prefix "" or the locale) also by its id. Throws on a name that is no unit.
429
+ * The units `--unit` names: by prefix; a version served at the root (prefix "", the locale or
430
+ * the product) also by its id, and inside a product by `<product>/<id>`; and a product by its
431
+ * name, which is all its versions and locales. Throws on a name that is no unit.
404
432
  */
405
433
  export declare function selectUnits(allUnits: BuildUnit[], names: string[] | undefined): BuildUnit[];
406
434
  /**
@@ -31,8 +31,7 @@ export type FrontMatterOf = (path: string, c: FileContent) => Promise<Record<str
31
31
  */
32
32
  export declare function frontMatterCache(read: Read): FrontMatterOf;
33
33
  /**
34
- * One table per language of the default version, keyed by locale (`""`
35
- * without a locale axis). Empty when none of the units being built is an
34
+ * One table per language of the default version and per product, keyed by `tableKey`. Empty when none of the units being built is an
36
35
  * older version (then nothing points elsewhere, and nothing is read).
37
36
  */
38
37
  export declare function defaultTables(cfg: SiteConfig, units: BuildUnit[], built: BuildUnit[], files: Map<string, FileContent>, read: Read, warn?: (m: string) => void, fm?: FrontMatterOf): Promise<Map<string, DefaultTable>>;
@@ -1,3 +1,4 @@
1
+ import { type ProductModel } from "@dogsbay/site-files";
1
2
  export type LinkStyle = "html" | "pretty";
2
3
  export type TocMode = "top" | "popover" | "rail" | "off";
3
4
  export interface VersionDef {
@@ -14,9 +15,26 @@ export interface VersionDef {
14
15
  noindex?: boolean;
15
16
  /** Override whether the version's pages are in the sitemap. */
16
17
  sitemap?: boolean;
17
- /** `""`: the version is served at the root (no version segment). Only the default version may set it. */
18
- path?: "";
18
+ /**
19
+ * The version's URL segment when it is not its id. `""`: served at the root (no version segment),
20
+ * only on the default version. Any other segment (`latest`) only inside `content.products:`.
21
+ */
22
+ path?: string;
23
+ /** The version's own banner text (`content.products:` only), instead of the standard one. */
24
+ banner?: string;
25
+ }
26
+ /** A call into @dogsbay/site-files' product model, its config errors reported as ours (dev and the CLI tell them apart). */
27
+ export declare function shared<T>(f: () => T): T;
28
+ /** The versions a unit's product has, and its default. */
29
+ export interface ProductVersions {
30
+ versions: VersionDef[];
31
+ defaultVersion?: string;
19
32
  }
33
+ /**
34
+ * A product's versions and default: its own with `content.products:`, else the site-wide `versions:`
35
+ * and `defaultVersion` (the unnamed source's, and every product's without the block).
36
+ */
37
+ export declare function versionsOf(cfg: Pick<SiteConfig, "versions" | "defaultVersion" | "productVersions">, product: string | undefined): ProductVersions;
20
38
  export interface LocaleDef {
21
39
  id: string;
22
40
  label: string;
@@ -72,6 +90,10 @@ export interface SiteConfig {
72
90
  routablesFrom: "all" | "nav";
73
91
  versions: VersionDef[];
74
92
  defaultVersion?: string;
93
+ /** `content.products:`, read by @dogsbay/site-files' `productModel` (plans/product-versions-ssg.md). Undefined without the block. */
94
+ products?: ProductModel;
95
+ /** With `products`: each product's versions and default (key `""` for the unnamed source's), read through `versionsOf`. */
96
+ productVersions?: Map<string, ProductVersions>;
75
97
  locales: LocaleDef[];
76
98
  defaultLocale?: string;
77
99
  search: boolean;
@@ -274,10 +296,26 @@ export interface BuildUnit {
274
296
  /** Every source the unit builds, the main one included, in config order. */
275
297
  members: UnitMember[];
276
298
  version?: string;
299
+ /** The version's URL segment: its id, its `path` (`latest`), or `""` when served without one. */
300
+ versionPath?: string;
277
301
  locale?: string;
278
- /** Output prefix inside the site, e.g. "en/3.31" or "" when neither axis is active. */
302
+ /**
303
+ * The named source the unit publishes, when named sources are combined with versions or locales
304
+ * (a product): its first prefix segment, as the CLI's `/<name>/<locale>/<version>/` order.
305
+ * Units of one product share it; the unnamed source's units have none.
306
+ */
307
+ product?: string;
308
+ /** Output prefix inside the site, e.g. "en/3.31", "calico/3.31", or "" when no axis is active. */
279
309
  prefix: string;
280
310
  }
311
+ /**
312
+ * The prefix of a unit's product and locale, without its version: where the unit's
313
+ * versions sit side by side ("calico/en", "en", or ""). A version served at the root
314
+ * (`path: ""`) has this as its prefix.
315
+ */
316
+ export declare function groupPrefix(u: Pick<BuildUnit, "product" | "locale">): string;
317
+ /** Whether two units are versions or languages of the same product (or both of no product). */
318
+ export declare const sameProduct: (a: Pick<BuildUnit, "product">, b: Pick<BuildUnit, "product">) => boolean;
281
319
  /**
282
320
  * Expand sources into units. An axis is "active" (adds a URL segment)
283
321
  * when the sources carry two or more distinct values for it, or when
@@ -29,7 +29,7 @@
29
29
  * Node-free: the dev server (src/node/dev.ts) owns the file map and the
30
30
  * watcher, and updates `input.files` before calling `change`.
31
31
  */
32
- import { type BuildProblem, type PageDiagnostic, type BuildInput, type OutputContent } from "./build.js";
32
+ import { type BuildNote, type BuildProblem, type PageDiagnostic, type BuildInput, type OutputContent } from "./build.js";
33
33
  import { type SiteConfig } from "./config.js";
34
34
  export interface DevSession {
35
35
  config: SiteConfig;
@@ -45,6 +45,8 @@ export interface DevSession {
45
45
  takeWarnings(): string[];
46
46
  /** What the site's build says that is not a problem (BuildResult.notes: a content page standing as a mounted API's root), as it stands now. */
47
47
  notes(): string[];
48
+ /** The same notes as data: each with the page it is about, its source file (a project path) and its path in the site. */
49
+ noteItems(): BuildNote[];
48
50
  /**
49
51
  * The same warnings as data, not handed out yet (its own count: taking one kind leaves the other):
50
52
  * the file each is about as a project path, with its line when known, and a code.
@@ -101,6 +103,13 @@ export interface DevOptions {
101
103
  search?: boolean;
102
104
  /** Build llms.txt, its section files and llms-full.txt at the start (default: no). */
103
105
  llms?: boolean;
106
+ /**
107
+ * Mark each block with its line in the page's file (`data-line`), for an editor's scroll sync.
108
+ * Lines templating moved are left out (`sourceLinesOf` in build.ts). Default: no.
109
+ */
110
+ dataLines?: boolean;
111
+ /** `false`: leave math as LaTeX (`<code class="math-display">` and inline), for a host that renders it itself. Default: as the config says. */
112
+ math?: boolean;
104
113
  }
105
114
  export declare function openDevSession(input: BuildInput & {
106
115
  configText?: string;
@@ -19,7 +19,7 @@ export declare function openDevSession(input: BuildInput & {
19
19
  configText?: string;
20
20
  }, o?: DevOptions): Promise<DevSession>;
21
21
  export { prebuiltSiteCss };
22
- export type { BuildInput, BuildResult, FileContent, OutputContent, SinkContent, SiteCssBuilder, BuiltPage, HighlightPool, BuildProblem, PageDiagnostic } from "../core/build.js";
22
+ export type { BuildInput, BuildResult, FileContent, OutputContent, SinkContent, SiteCssBuilder, BuiltPage, HighlightPool, BuildProblem, BuildNote, PageDiagnostic } from "../core/build.js";
23
23
  export { PageClashError } from "../core/build.js";
24
24
  export type { ChangeResult, DevOptions, DevSession } from "../core/dev.js";
25
25
  export { parseConfig, projectRoots, ConfigError, type SiteConfig, type DiagramEngine } from "../core/config.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/ssg",
3
- "version": "0.2.0-beta.143",
3
+ "version": "0.2.0-beta.146",
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",
@@ -49,38 +49,38 @@
49
49
  "tailwindcss": "^4.3.2"
50
50
  },
51
51
  "peerDependencies": {
52
- "@dogsbay/elements": "0.2.0-beta.143",
53
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.143",
54
- "@dogsbay/format-openapi": "0.2.0-beta.143",
55
- "@dogsbay/format-topicmap": "0.2.0-beta.143",
56
- "@dogsbay/highlight": "0.2.0-beta.143",
57
- "@dogsbay/minja": "0.2.0-beta.143",
58
- "@dogsbay/primitives": "0.2.0-beta.143",
59
- "@dogsbay/render-html": "0.2.0-beta.143",
60
- "@dogsbay/search": "0.2.0-beta.143",
61
- "@dogsbay/serialize-core": "0.2.0-beta.143",
62
- "@dogsbay/site-check": "0.2.0-beta.143",
63
- "@dogsbay/site-files": "0.2.0-beta.143",
64
- "@dogsbay/types": "0.2.0-beta.143"
52
+ "@dogsbay/elements": "0.2.0-beta.146",
53
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.146",
54
+ "@dogsbay/format-openapi": "0.2.0-beta.146",
55
+ "@dogsbay/format-topicmap": "0.2.0-beta.146",
56
+ "@dogsbay/highlight": "0.2.0-beta.146",
57
+ "@dogsbay/minja": "0.2.0-beta.146",
58
+ "@dogsbay/primitives": "0.2.0-beta.146",
59
+ "@dogsbay/render-html": "0.2.0-beta.146",
60
+ "@dogsbay/search": "0.2.0-beta.146",
61
+ "@dogsbay/serialize-core": "0.2.0-beta.146",
62
+ "@dogsbay/site-check": "0.2.0-beta.146",
63
+ "@dogsbay/site-files": "0.2.0-beta.146",
64
+ "@dogsbay/types": "0.2.0-beta.146"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@axe-core/playwright": "^4.12.1",
68
68
  "@cloudflare/workers-types": "^5.20260921.1",
69
- "@dogsbay/elements": "0.2.0-beta.143",
70
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.143",
71
- "@dogsbay/format-openapi": "0.2.0-beta.143",
72
- "@dogsbay/format-topicmap": "0.2.0-beta.143",
73
- "@dogsbay/highlight": "0.2.0-beta.143",
74
- "@dogsbay/icons": "0.2.0-beta.143",
75
- "@dogsbay/minja": "0.2.0-beta.143",
76
- "@dogsbay/primitives": "0.2.0-beta.143",
77
- "@dogsbay/render-html": "0.2.0-beta.143",
78
- "@dogsbay/search": "0.2.0-beta.143",
79
- "@dogsbay/serialize-core": "0.2.0-beta.143",
80
- "@dogsbay/site-check": "0.2.0-beta.143",
81
- "@dogsbay/site-files": "0.2.0-beta.143",
82
- "@dogsbay/types": "0.2.0-beta.143",
83
- "@dogsbay/ui": "0.2.0-beta.143",
69
+ "@dogsbay/elements": "0.2.0-beta.146",
70
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.146",
71
+ "@dogsbay/format-openapi": "0.2.0-beta.146",
72
+ "@dogsbay/format-topicmap": "0.2.0-beta.146",
73
+ "@dogsbay/highlight": "0.2.0-beta.146",
74
+ "@dogsbay/icons": "0.2.0-beta.146",
75
+ "@dogsbay/minja": "0.2.0-beta.146",
76
+ "@dogsbay/primitives": "0.2.0-beta.146",
77
+ "@dogsbay/render-html": "0.2.0-beta.146",
78
+ "@dogsbay/search": "0.2.0-beta.146",
79
+ "@dogsbay/serialize-core": "0.2.0-beta.146",
80
+ "@dogsbay/site-check": "0.2.0-beta.146",
81
+ "@dogsbay/site-files": "0.2.0-beta.146",
82
+ "@dogsbay/types": "0.2.0-beta.146",
83
+ "@dogsbay/ui": "0.2.0-beta.146",
84
84
  "@mermanjs/web": "0.7.0",
85
85
  "@playwright/test": "1.61.1",
86
86
  "@types/node": "^22.10.0",