@dogsbay/ssg 0.2.0-beta.151 → 0.2.0-beta.151.1

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.
@@ -1207,15 +1207,12 @@ function renderRedirectPage(rule, cfg) {
1207
1207
  const canonical = isExternal(rule.to) ? rule.to : rule.toOut ? absoluteUrl(cfg, "", servedPath(rule.toOut, cfg.linkStyle)) : void 0;
1208
1208
  return renderRedirectStub(href, canonical);
1209
1209
  }
1210
- function renderRedirectsFile(rules, cfg) {
1210
+ function servedRedirect(r, cfg) {
1211
1211
  const served = (sitePathOrOut) => "/" + [cfg.basePath, sitePathOrOut].filter(Boolean).join("/");
1212
- return renderRedirectsLines(
1213
- rules.map((r) => ({
1214
- from: served(r.from),
1215
- to: isExternal(r.to) ? r.to : served(r.toOut ? servedPath(r.toOut, cfg.linkStyle) : r.to) + (r.suffix ?? ""),
1216
- status: r.status
1217
- }))
1218
- );
1212
+ return { from: served(r.from), to: isExternal(r.to) ? r.to : served(r.toOut ? servedPath(r.toOut, cfg.linkStyle) : r.to) + (r.suffix ?? ""), status: r.status };
1213
+ }
1214
+ function renderRedirectsFile(rules, cfg) {
1215
+ return renderRedirectsLines(rules.map((r) => servedRedirect(r, cfg)));
1219
1216
  }
1220
1217
 
1221
1218
  // src/core/canonical.ts
@@ -1362,6 +1359,8 @@ var CLIENT_JS = `/*! dogsbay-ssg site client (Apache-2.0). Third-party notices:
1362
1359
  Expects a CSS selector, a Node element, a NodeList or an array.
1363
1360
  See: https://github.com/francoischalifour/medium-zoom\`)}},no=function(t){var e=document.createElement("div");return e.classList.add("medium-zoom-overlay"),e.style.background=t,e},so=function(t){var e=t.getBoundingClientRect(),n=e.top,s=e.left,o=e.width,r=e.height,a=t.cloneNode(),l=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,c=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;return a.removeAttribute("id"),a.style.position="absolute",a.style.top=n+l+"px",a.style.left=s+c+"px",a.style.width=o+"px",a.style.height=r+"px",a.style.transform="",a},kt=function(t,e){var n=pt({bubbles:!1,cancelable:!1,detail:void 0},e);if(typeof window.CustomEvent=="function")return new CustomEvent(t,n);var s=document.createEvent("CustomEvent");return s.initCustomEvent(t,n.bubbles,n.cancelable,n.detail),s},oo=function i(t){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=window.Promise||function(x){function E(){}x(E,E)},s=function(x){var E=x.target;if(E===q){p();return}L.indexOf(E)!==-1&&b({target:E})},o=function(){if(!(I||!v.original)){var x=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(H-x)>O.scrollOffset&&setTimeout(p,150)}},r=function(x){var E=x.key||x.keyCode;(E==="Escape"||E==="Esc"||E===27)&&p()},a=function(){var x=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=x;if(x.background&&(q.style.background=x.background),x.container&&x.container instanceof Object&&(E.container=pt({},O.container,x.container)),x.template){var C=ne(x.template)?x.template:document.querySelector(x.template);E.template=C}return O=pt({},O,E),L.forEach(function(T){T.dispatchEvent(kt("medium-zoom:update",{detail:{zoom:M}}))}),M},l=function(){var x=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};return i(pt({},O,x))},c=function(){for(var x=arguments.length,E=Array(x),C=0;C<x;C++)E[C]=arguments[C];var T=E.reduce(function(A,k){return[].concat(A,rn(k))},[]);return T.filter(function(A){return L.indexOf(A)===-1}).forEach(function(A){L.push(A),A.classList.add("medium-zoom-image")}),R.forEach(function(A){var k=A.type,D=A.listener,_=A.options;T.forEach(function(P){P.addEventListener(k,D,_)})}),M},d=function(){for(var x=arguments.length,E=Array(x),C=0;C<x;C++)E[C]=arguments[C];v.zoomed&&p();var T=E.length>0?E.reduce(function(A,k){return[].concat(A,rn(k))},[]):L;return T.forEach(function(A){A.classList.remove("medium-zoom-image"),A.dispatchEvent(kt("medium-zoom:detach",{detail:{zoom:M}}))}),L=L.filter(function(A){return T.indexOf(A)===-1}),M},u=function(x,E){var C=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return L.forEach(function(T){T.addEventListener("medium-zoom:"+x,E,C)}),R.push({type:"medium-zoom:"+x,listener:E,options:C}),M},f=function(x,E){var C=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return L.forEach(function(T){T.removeEventListener("medium-zoom:"+x,E,C)}),R=R.filter(function(T){return!(T.type==="medium-zoom:"+x&&T.listener.toString()===E.toString())}),M},m=function(){var x=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=x.target,C=function(){var A={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},k=void 0,D=void 0;if(O.container)if(O.container instanceof Object)A=pt({},A,O.container),k=A.width-A.left-A.right-O.margin*2,D=A.height-A.top-A.bottom-O.margin*2;else{var _=ne(O.container)?O.container:document.querySelector(O.container),P=_.getBoundingClientRect(),tt=P.width,qt=P.height,dn=P.left,un=P.top;A=pt({},A,{width:tt,height:qt,left:dn,top:un})}k=k||A.width-O.margin*2,D=D||A.height-O.margin*2;var Ot=v.zoomedHd||v.original,hn=on(Ot)?k:Ot.naturalWidth||k,fn=on(Ot)?D:Ot.naturalHeight||D,Kt=Ot.getBoundingClientRect(),mn=Kt.top,pn=Kt.left,ae=Kt.width,le=Kt.height,gn=Math.min(Math.max(ae,hn),k)/ae,bn=Math.min(Math.max(le,fn),D)/le,ce=Math.min(gn,bn),vn=(-pn+(k-ae)/2+O.margin+A.left)/ce,yn=(-mn+(D-le)/2+O.margin+A.top)/ce,Re="scale("+ce+") translate3d("+vn+"px, "+yn+"px, 0)";v.zoomed.style.transform=Re,v.zoomedHd&&(v.zoomedHd.style.transform=Re)};return new n(function(T){if(E&&L.indexOf(E)===-1){T(M);return}var A=function tt(){I=!1,v.zoomed.removeEventListener("transitionend",tt),v.original.dispatchEvent(kt("medium-zoom:opened",{detail:{zoom:M}})),T(M)};if(v.zoomed){T(M);return}if(E)v.original=E;else if(L.length>0){var k=L;v.original=k[0]}else{T(M);return}if(v.original.dispatchEvent(kt("medium-zoom:open",{detail:{zoom:M}})),H=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,I=!0,v.zoomed=so(v.original),document.body.appendChild(q),O.template){var D=ne(O.template)?O.template:document.querySelector(O.template);v.template=document.createElement("div"),v.template.appendChild(D.content.cloneNode(!0)),document.body.appendChild(v.template)}if(v.original.parentElement&&v.original.parentElement.tagName==="PICTURE"&&v.original.currentSrc&&(v.zoomed.src=v.original.currentSrc),document.body.appendChild(v.zoomed),window.requestAnimationFrame(function(){document.body.classList.add("medium-zoom--opened")}),v.original.classList.add("medium-zoom-image--hidden"),v.zoomed.classList.add("medium-zoom-image--opened"),v.zoomed.addEventListener("click",p),v.zoomed.addEventListener("transitionend",A),v.original.getAttribute("data-zoom-src")){v.zoomedHd=v.zoomed.cloneNode(),v.zoomedHd.removeAttribute("srcset"),v.zoomedHd.removeAttribute("sizes"),v.zoomedHd.removeAttribute("loading"),v.zoomedHd.src=v.zoomed.getAttribute("data-zoom-src"),v.zoomedHd.onerror=function(){clearInterval(_),console.warn("Unable to reach the zoom image target "+v.zoomedHd.src),v.zoomedHd=null,C()};var _=setInterval(function(){v.zoomedHd.complete&&(clearInterval(_),v.zoomedHd.classList.add("medium-zoom-image--opened"),v.zoomedHd.addEventListener("click",p),document.body.appendChild(v.zoomedHd),C())},10)}else if(v.original.hasAttribute("srcset")){v.zoomedHd=v.zoomed.cloneNode(),v.zoomedHd.removeAttribute("sizes"),v.zoomedHd.removeAttribute("loading");var P=v.zoomedHd.addEventListener("load",function(){v.zoomedHd.removeEventListener("load",P),v.zoomedHd.classList.add("medium-zoom-image--opened"),v.zoomedHd.addEventListener("click",p),document.body.appendChild(v.zoomedHd),C()})}else C()})},p=function(){return new n(function(x){if(I||!v.original){x(M);return}var E=function C(){v.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(v.zoomed),v.zoomedHd&&document.body.removeChild(v.zoomedHd),document.body.removeChild(q),v.zoomed.classList.remove("medium-zoom-image--opened"),v.template&&document.body.removeChild(v.template),I=!1,v.zoomed.removeEventListener("transitionend",C),v.original.dispatchEvent(kt("medium-zoom:closed",{detail:{zoom:M}})),v.original=null,v.zoomed=null,v.zoomedHd=null,v.template=null,x(M)};I=!0,document.body.classList.remove("medium-zoom--opened"),v.zoomed.style.transform="",v.zoomedHd&&(v.zoomedHd.style.transform=""),v.template&&(v.template.style.transition="opacity 150ms",v.template.style.opacity=0),v.original.dispatchEvent(kt("medium-zoom:close",{detail:{zoom:M}})),v.zoomed.addEventListener("transitionend",E)})},b=function(){var x=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=x.target;return v.original?p():m({target:E})},g=function(){return O},y=function(){return L},w=function(){return v.original},L=[],R=[],I=!1,H=0,O=e,v={original:null,zoomed:null,zoomedHd:null,template:null};Object.prototype.toString.call(t)==="[object Object]"?O=t:(t||typeof t=="string")&&c(t),O=pt({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},O);var q=no(O.background);document.addEventListener("click",s),document.addEventListener("keyup",r),document.addEventListener("scroll",o),window.addEventListener("resize",p);var M={open:m,close:p,toggle:b,update:a,clone:l,attach:c,detach:d,on:u,off:f,getOptions:g,getImages:y,getZoomedImage:w};return M};function ro(i,t){t===void 0&&(t={});var e=t.insertAt;if(!(!i||typeof document>"u")){var n=document.head||document.getElementsByTagName("head")[0],s=document.createElement("style");s.type="text/css",e==="top"&&n.firstChild?n.insertBefore(s,n.firstChild):n.appendChild(s),s.styleSheet?s.styleSheet.cssText=i:s.appendChild(document.createTextNode(i))}}var ao=".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}";ro(ao);var an=oo;var lo;function ln(i){let t=document.body.dataset.imageZoom;if(!t)return;let e;try{e=JSON.parse(t)}catch{return}let n=[...document.querySelectorAll('img[data-zoomable="true"]')];if(!n.length)return;let s=lo??(lo=an());s.update(e),s.attach(...n),i==null||i.addEventListener("abort",()=>{s.close(),s.detach(...n)});let o={signal:i},r=!1,a=l=>l.key==="Enter"||l.key===" ";for(let l of n)l.alt&&(l.tabIndex=0,l.addEventListener("keydown",c=>{a(c)&&(c.preventDefault(),r=!0,s.open({target:l}))},o),l.addEventListener("medium-zoom:opened",()=>{let c=document.querySelector(".medium-zoom-image--opened");!r||!c||(c.tabIndex=-1,c.addEventListener("keydown",d=>{a(d)&&(d.preventDefault(),s.close())}),c.focus())},o),l.addEventListener("medium-zoom:closed",()=>{r&&l.focus(),r=!1},o))}function se(i){return!/^[\\x00-\\x20]*([a-z][a-z0-9+.-]*:|[\\\\/])/i.test(i.replace(/[\\t\\n\\r]/g,""))}var Oe=new Map;function cn(i){let t=Oe.get(i);return t||(t=new Promise((e,n)=>{let s=document.createElement("script");s.src=i,s.onload=()=>e(),s.onerror=()=>{Oe.delete(i),n(new Error(\`could not load \${i}\`))},document.head.appendChild(s)}),Oe.set(i,t)),t}function co(i){document.querySelectorAll("[data-component='theme-toggle']").forEach(t=>{let e=()=>t.setAttribute("aria-pressed",String(document.documentElement.classList.contains("dark")));e(),t.addEventListener("click",()=>{let n=!document.documentElement.classList.contains("dark");document.documentElement.classList.toggle("dark",n);try{localStorage.setItem("theme",n?"dark":"light")}catch{}e()},{signal:i})})}function uo(i){document.addEventListener("click",async t=>{var s,o,r,a;let e=t.target.closest("[data-copy-markdown]");if(!e)return;let n=(((s=document.getElementById("db-page-markdown"))==null?void 0:s.textContent)??"").replace(/<\\\\\\/(script)/gi,"</$1").replace(/<\\\\!--/g,"<!--");try{await navigator.clipboard.writeText(n);let l=(o=e.dataset).label??(o.label=e.textContent??"");e.textContent="Copied",clearTimeout(Number(e.dataset.timer)),e.dataset.timer=String(setTimeout(()=>e.textContent=l,1500))}catch{}(a=(r=e.closest("db-popover"))==null?void 0:r.close)==null||a.call(r,"close-press")},{signal:i})}function ho(i,t){let e=document.body.dataset.page??"index.html";document.querySelectorAll("select[data-db-switch]").forEach(n=>{n.addEventListener("change",async()=>{var a;let s=n.selectedOptions[0];if(!s||s.disabled||!s.hasAttribute("value"))return;let o=s.dataset.prefix??"",r=n.value;if(s.hasAttribute("data-link")){(se(r)||/^https?:\\/\\//i.test(r))&&(location.href=r);return}if(!(!se(r)||!se(o))){if(n.dataset.dbSwitch==="product"){location.href=r;return}try{let l=o?\`\${i}\${o}/\`:i;await cn(\`\${l}_dogsbay/pages.js\`);let c=(a=window.__DOGSBAY_PAGES__)==null?void 0:a[o];location.href=c&&!c.includes(e)?\`\${l}index.html\`:r}catch{location.href=r}}},{signal:t})})}function fo(i){document.addEventListener("click",t=>{var n;let e=t.target.closest("a.db-anchor");!e||location.protocol==="file:"||(n=navigator.clipboard)==null||n.writeText(e.href).catch(()=>{})},{signal:i})}var Bt=new Map,re=new Set;function oe(){for(let[i,t]of Bt)i.isConnected||(ot(i),t.abort(),Bt.delete(i));for(let i of re)i.isConnected||ot(i)}function mo(i,t){let e=EventTarget.prototype,n=e.addEventListener,s=0;e.addEventListener=function(o,r,a){s++;let l=typeof a=="object"?a.signal:void 0;n.call(this,o,r,typeof a=="object"?{...a,signal:l?AbortSignal.any([l,i]):i}:{capture:a===!0,signal:i})};try{t()}finally{e.addEventListener=n}return s}function po(){oe(),document.querySelectorAll("[data-component='sidebar-provider']").forEach(i=>{if(Bt.has(i)||re.has(i))return;let t=new AbortController;mo(t.signal,()=>Se(i))?Bt.set(i,t):i.querySelector("[data-component='sidebar']")&&re.add(i)}),document.removeEventListener("keydown",oe),document.addEventListener("keydown",oe)}var Tt;function De(i={}){Tt==null||Tt.unmount();let t=new AbortController,{signal:e}=t,n=document.body,s=n.dataset.unitRoot??"",o=n.dataset.siteRoot??"";i.nav!==void 0&&(window.__DOGSBAY_NAV__=i.nav),po(),i.theme!==!1&&co(e),uo(e),i.switchers!==!1&&ho(o,e),fo(e),i.search!==!1&&en({unitRoot:s,loadScript:cn,signal:e}),sn(e),ln(e);let r={unmount(){if(!e.aborted){t.abort();for(let a of[...Bt.keys(),...re])ot(a);oe(),Tt===r&&(Tt=void 0)}}};return Tt=r}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>De()):De();})();
1364
1361
  `;
1362
+ var CLIENT_FILE = "dogsbay-4e1b1719ed.js";
1363
+ var SSG_VERSION = "0.2.0-beta.151.1";
1365
1364
  var THEME_TEMPLATES = { "layouts/blank.html": '{# A page without the sidebar or table of contents: landing pages, splash pages, 404. -#}\n<!doctype html>\n<html lang="{{ page.lang }}" dir="{{ page.dir }}"{% if site.colorMode == "dark" %} class="dark"{% endif %}>\n<head>\n{% include "partials/head.html" %}\n</head>\n<body class="bg-background text-foreground antialiased" data-unit-root="{{ paths.unit }}" data-site-root="{{ paths.site }}" data-prefix="{{ page.prefix }}" data-page="{{ page.out }}"{{ bodyAttrs }}>\n{{ skipLink }}\n<div class="relative flex min-h-svh w-full flex-col bg-background">\n{{ announcementBar }}\n{% include "partials/header.html" %}\n<main id="db-content" class="min-w-0 flex-1 px-6 py-8 outline-none lg:px-12" tabindex="-1">\n<div class="mx-auto {{ widthClass }}">\n{{ banner }}{{ versionBadge }}{{ h1 }}{{ byline }}{{ lede }}{{ hero }}{{ metaStrip }}\n<article class="docs-prose">\n{{ content }}\n</article>\n{% include "partials/footer.html" %}\n</div>\n</main>\n</div>\n{{ searchDialog }}\n{{ scripts }}\n</body>\n</html>\n', "layouts/docs.html": '{# The docs layout: a port of @dogsbay/docs-layout DocsLayout.astro. -#}\n<!doctype html>\n<html lang="{{ page.lang }}" dir="{{ page.dir }}"{% if site.colorMode == "dark" %} class="dark"{% endif %}>\n<head>\n{% include "partials/head.html" %}\n</head>\n<body class="bg-background text-foreground antialiased" data-unit-root="{{ paths.unit }}" data-site-root="{{ paths.site }}" data-prefix="{{ page.prefix }}" data-page="{{ page.out }}"{{ bodyAttrs }}>\n{{ skipLink }}\n<div class="group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar" data-component="sidebar-provider" style="--sidebar-width: 16rem; --sidebar-width-icon: 3rem;">\n{{ sidebar }}\n<div class="relative flex w-full min-w-0 flex-1 flex-col bg-background">\n{{ announcementBar }}\n{% include "partials/header.html" %}\n<div class="flex min-w-0">\n<main id="db-content" class="min-w-0 flex-1 px-6 py-8 outline-none lg:px-12" tabindex="-1">\n<div class="mx-auto {{ widthClass }}">\n{{ banner }}{{ breadcrumb }}{{ pageActionsInline }}{{ versionBadge }}{{ h1 }}{{ byline }}{{ lede }}{{ hero }}{{ metaStrip }}{{ tocTop }}\n<article class="docs-prose">\n{{ content }}\n</article>\n{% include "partials/footer.html" %}\n</div>\n</main>\n{{ tocRail }}\n</div>\n</div>\n</div>\n{{ searchDialog }}\n{{ scripts }}\n</body>\n</html>\n', "partials/announcement.html": `{#- The site's announcement bar (site.announcement), as DocsLayout's AnnouncementBar.astro: a band above the
1366
1365
  header in the callout colours of its tone. A closeable one is closed and remembered by <db-announcement>,
1367
1366
  and the head script hides a closed one before first paint. Context: announcement = {id, key (what a close is
@@ -2643,7 +2642,7 @@ async function composePage(p, cfg, templates) {
2643
2642
  // into its escaped script states; the client reverses both before copying.
2644
2643
  (p.markdown != null && cfg.pageActions.enabled && cfg.pageActions.copyButton ? `<script type="text/markdown" id="db-page-markdown">${p.markdown.replace(/<\/(script)/gi, "<\\/$1").replace(/<!--/g, "<\\!--")}<\/script>
2645
2644
  ` : "") + `<script src="${esc2(p.unitRoot)}_dogsbay/nav.js" defer><\/script>
2646
- <script src="${esc2(p.siteRoot)}_dogsbay/assets/dogsbay.js" defer><\/script>`
2645
+ <script src="${esc2(p.siteRoot)}_dogsbay/assets/${CLIENT_FILE}" defer><\/script>`
2647
2646
  )
2648
2647
  };
2649
2648
  return templates.render(p.layout, ctx);
@@ -3115,6 +3114,264 @@ function strings(locale, overrides) {
3115
3114
  return (key) => table[key] ?? "";
3116
3115
  }
3117
3116
 
3117
+ // src/core/unit-manifest.ts
3118
+ import { unitKeyString, unitManifest } from "@dogsbay/site-files";
3119
+ function unitKey(u) {
3120
+ return unitKeyString({ product: u.product, locale: u.locale, version: u.version });
3121
+ }
3122
+ function manifestPath(u) {
3123
+ return `_dogsbay/units/${unitKey(u)}.json`;
3124
+ }
3125
+ function makeManifest(u, ssg, redirects) {
3126
+ const inside = (p) => !u.prefix ? p : p === u.prefix ? "" : p.startsWith(`${u.prefix}/`) ? p.slice(u.prefix.length + 1) : p;
3127
+ return { ...unitManifest({ product: u.product, locale: u.locale, version: u.version }, { pages: ssg.pageOut.map(([p]) => inside(p)), sitemap: [], redirects }), ssg };
3128
+ }
3129
+ function parseManifest(raw) {
3130
+ let v = raw;
3131
+ if (typeof raw === "string") {
3132
+ try {
3133
+ v = JSON.parse(raw);
3134
+ } catch (e) {
3135
+ return { problem: `not JSON (${e.message})` };
3136
+ }
3137
+ }
3138
+ const m = v;
3139
+ if (!m || typeof m !== "object" || m.schemaVersion !== 1) return { problem: "not a unit manifest (schemaVersion 1)" };
3140
+ const s = m.ssg;
3141
+ const strings2 = (x) => Array.isArray(x) && x.every((y) => typeof y === "string");
3142
+ const pairs = (x) => Array.isArray(x) && x.every((y) => Array.isArray(y) && y.length === 2 && typeof y[0] === "string" && typeof y[1] === "string");
3143
+ if (!s || typeof s !== "object" || typeof s.prefix !== "string" || !Array.isArray(s.redirectInputs) || !pairs(s.pageOut) || !strings2(s.sitemaps) || !strings2(s.files) || typeof s.client !== "string" || typeof s.builder !== "string") {
3144
+ return { problem: "has no dogsbay-ssg section (written by another tool, or an older dogsbay-ssg)" };
3145
+ }
3146
+ if (s.math !== void 0 && typeof s.math !== "boolean" || s.iconSets !== void 0 && !strings2(s.iconSets)) return { problem: "has a malformed notices record" };
3147
+ const triples = (x) => Array.isArray(x) && x.every((y) => Array.isArray(y) && y.length === 3 && typeof y[0] === "string" && typeof y[1] === "string" && typeof y[2] === "number");
3148
+ if (!triples(m.redirects)) return { problem: "has malformed redirects" };
3149
+ const inputs = s.redirectInputs;
3150
+ if (!inputs.every((r) => !!r && typeof r === "object" && typeof r.from === "string" && typeof r.to === "string" && typeof r.status === "number")) {
3151
+ return { problem: "has a malformed redirect" };
3152
+ }
3153
+ return { manifest: m };
3154
+ }
3155
+
3156
+ // src/generated/icon-notices.ts
3157
+ var ICON_SET_NOTICES = { "fa6-brands": "Font Awesome 6 Brands icons (CC-BY-4.0), by Dave Gandy\nhttps://github.com/FortAwesome/Font-Awesome\nLicence: https://creativecommons.org/licenses/by/4.0/", "fa6-regular": "Font Awesome 6 Regular icons (CC-BY-4.0), by Dave Gandy\nhttps://github.com/FortAwesome/Font-Awesome\nLicence: https://creativecommons.org/licenses/by/4.0/", "fa6-solid": "Font Awesome 6 Solid icons (CC-BY-4.0), by Dave Gandy\nhttps://github.com/FortAwesome/Font-Awesome\nLicence: https://creativecommons.org/licenses/by/4.0/", "lucide": 'Lucide icons (ISC), by Lucide Contributors\nhttps://github.com/lucide-icons/lucide\nLicence: https://github.com/lucide-icons/lucide/blob/main/LICENSE\n\nISC License\n\nCopyright (c) 2026 Lucide Icons and Contributors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n---\n\nThe following Lucide icons are derived from the Feather project:\n\nairplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out\n\nThe MIT License (MIT) (for the icons listed above)\n\nCopyright (c) 2013-present Cole Bemis\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.', "mdi": `Material Design Icons (Apache-2.0), by Pictogrammers
3158
+ https://github.com/Templarian/MaterialDesign
3159
+ Licence: https://github.com/Templarian/MaterialDesign/blob/master/LICENSE
3160
+
3161
+ Apache License
3162
+ Version 2.0, January 2004
3163
+ http://www.apache.org/licenses/
3164
+
3165
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
3166
+
3167
+ 1. Definitions.
3168
+
3169
+ "License" shall mean the terms and conditions for use, reproduction,
3170
+ and distribution as defined by Sections 1 through 9 of this document.
3171
+
3172
+ "Licensor" shall mean the copyright owner or entity authorized by
3173
+ the copyright owner that is granting the License.
3174
+
3175
+ "Legal Entity" shall mean the union of the acting entity and all
3176
+ other entities that control, are controlled by, or are under common
3177
+ control with that entity. For the purposes of this definition,
3178
+ "control" means (i) the power, direct or indirect, to cause the
3179
+ direction or management of such entity, whether by contract or
3180
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
3181
+ outstanding shares, or (iii) beneficial ownership of such entity.
3182
+
3183
+ "You" (or "Your") shall mean an individual or Legal Entity
3184
+ exercising permissions granted by this License.
3185
+
3186
+ "Source" form shall mean the preferred form for making modifications,
3187
+ including but not limited to software source code, documentation
3188
+ source, and configuration files.
3189
+
3190
+ "Object" form shall mean any form resulting from mechanical
3191
+ transformation or translation of a Source form, including but
3192
+ not limited to compiled object code, generated documentation,
3193
+ and conversions to other media types.
3194
+
3195
+ "Work" shall mean the work of authorship, whether in Source or
3196
+ Object form, made available under the License, as indicated by a
3197
+ copyright notice that is included in or attached to the work
3198
+ (an example is provided in the Appendix below).
3199
+
3200
+ "Derivative Works" shall mean any work, whether in Source or Object
3201
+ form, that is based on (or derived from) the Work and for which the
3202
+ editorial revisions, annotations, elaborations, or other modifications
3203
+ represent, as a whole, an original work of authorship. For the purposes
3204
+ of this License, Derivative Works shall not include works that remain
3205
+ separable from, or merely link (or bind by name) to the interfaces of,
3206
+ the Work and Derivative Works thereof.
3207
+
3208
+ "Contribution" shall mean any work of authorship, including
3209
+ the original version of the Work and any modifications or additions
3210
+ to that Work or Derivative Works thereof, that is intentionally
3211
+ submitted to Licensor for inclusion in the Work by the copyright owner
3212
+ or by an individual or Legal Entity authorized to submit on behalf of
3213
+ the copyright owner. For the purposes of this definition, "submitted"
3214
+ means any form of electronic, verbal, or written communication sent
3215
+ to the Licensor or its representatives, including but not limited to
3216
+ communication on electronic mailing lists, source code control systems,
3217
+ and issue tracking systems that are managed by, or on behalf of, the
3218
+ Licensor for the purpose of discussing and improving the Work, but
3219
+ excluding communication that is conspicuously marked or otherwise
3220
+ designated in writing by the copyright owner as "Not a Contribution."
3221
+
3222
+ "Contributor" shall mean Licensor and any individual or Legal Entity
3223
+ on behalf of whom a Contribution has been received by Licensor and
3224
+ subsequently incorporated within the Work.
3225
+
3226
+ 2. Grant of Copyright License. Subject to the terms and conditions of
3227
+ this License, each Contributor hereby grants to You a perpetual,
3228
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
3229
+ copyright license to reproduce, prepare Derivative Works of,
3230
+ publicly display, publicly perform, sublicense, and distribute the
3231
+ Work and such Derivative Works in Source or Object form.
3232
+
3233
+ 3. Grant of Patent License. Subject to the terms and conditions of
3234
+ this License, each Contributor hereby grants to You a perpetual,
3235
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
3236
+ (except as stated in this section) patent license to make, have made,
3237
+ use, offer to sell, sell, import, and otherwise transfer the Work,
3238
+ where such license applies only to those patent claims licensable
3239
+ by such Contributor that are necessarily infringed by their
3240
+ Contribution(s) alone or by combination of their Contribution(s)
3241
+ with the Work to which such Contribution(s) was submitted. If You
3242
+ institute patent litigation against any entity (including a
3243
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
3244
+ or a Contribution incorporated within the Work constitutes direct
3245
+ or contributory patent infringement, then any patent licenses
3246
+ granted to You under this License for that Work shall terminate
3247
+ as of the date such litigation is filed.
3248
+
3249
+ 4. Redistribution. You may reproduce and distribute copies of the
3250
+ Work or Derivative Works thereof in any medium, with or without
3251
+ modifications, and in Source or Object form, provided that You
3252
+ meet the following conditions:
3253
+
3254
+ (a) You must give any other recipients of the Work or
3255
+ Derivative Works a copy of this License; and
3256
+
3257
+ (b) You must cause any modified files to carry prominent notices
3258
+ stating that You changed the files; and
3259
+
3260
+ (c) You must retain, in the Source form of any Derivative Works
3261
+ that You distribute, all copyright, patent, trademark, and
3262
+ attribution notices from the Source form of the Work,
3263
+ excluding those notices that do not pertain to any part of
3264
+ the Derivative Works; and
3265
+
3266
+ (d) If the Work includes a "NOTICE" text file as part of its
3267
+ distribution, then any Derivative Works that You distribute must
3268
+ include a readable copy of the attribution notices contained
3269
+ within such NOTICE file, excluding those notices that do not
3270
+ pertain to any part of the Derivative Works, in at least one
3271
+ of the following places: within a NOTICE text file distributed
3272
+ as part of the Derivative Works; within the Source form or
3273
+ documentation, if provided along with the Derivative Works; or,
3274
+ within a display generated by the Derivative Works, if and
3275
+ wherever such third-party notices normally appear. The contents
3276
+ of the NOTICE file are for informational purposes only and
3277
+ do not modify the License. You may add Your own attribution
3278
+ notices within Derivative Works that You distribute, alongside
3279
+ or as an addendum to the NOTICE text from the Work, provided
3280
+ that such additional attribution notices cannot be construed
3281
+ as modifying the License.
3282
+
3283
+ You may add Your own copyright statement to Your modifications and
3284
+ may provide additional or different license terms and conditions
3285
+ for use, reproduction, or distribution of Your modifications, or
3286
+ for any such Derivative Works as a whole, provided Your use,
3287
+ reproduction, and distribution of the Work otherwise complies with
3288
+ the conditions stated in this License.
3289
+
3290
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
3291
+ any Contribution intentionally submitted for inclusion in the Work
3292
+ by You to the Licensor shall be under the terms and conditions of
3293
+ this License, without any additional terms or conditions.
3294
+ Notwithstanding the above, nothing herein shall supersede or modify
3295
+ the terms of any separate license agreement you may have executed
3296
+ with Licensor regarding such Contributions.
3297
+
3298
+ 6. Trademarks. This License does not grant permission to use the trade
3299
+ names, trademarks, service marks, or product names of the Licensor,
3300
+ except as required for reasonable and customary use in describing the
3301
+ origin of the Work and reproducing the content of the NOTICE file.
3302
+
3303
+ 7. Disclaimer of Warranty. Unless required by applicable law or
3304
+ agreed to in writing, Licensor provides the Work (and each
3305
+ Contributor provides its Contributions) on an "AS IS" BASIS,
3306
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
3307
+ implied, including, without limitation, any warranties or conditions
3308
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
3309
+ PARTICULAR PURPOSE. You are solely responsible for determining the
3310
+ appropriateness of using or redistributing the Work and assume any
3311
+ risks associated with Your exercise of permissions under this License.
3312
+
3313
+ 8. Limitation of Liability. In no event and under no legal theory,
3314
+ whether in tort (including negligence), contract, or otherwise,
3315
+ unless required by applicable law (such as deliberate and grossly
3316
+ negligent acts) or agreed to in writing, shall any Contributor be
3317
+ liable to You for damages, including any direct, indirect, special,
3318
+ incidental, or consequential damages of any character arising as a
3319
+ result of this License or out of the use or inability to use the
3320
+ Work (including but not limited to damages for loss of goodwill,
3321
+ work stoppage, computer failure or malfunction, or any and all
3322
+ other commercial damages or losses), even if such Contributor
3323
+ has been advised of the possibility of such damages.
3324
+
3325
+ 9. Accepting Warranty or Additional Liability. While redistributing
3326
+ the Work or Derivative Works thereof, You may choose to offer,
3327
+ and charge a fee for, acceptance of support, warranty, indemnity,
3328
+ or other liability obligations and/or rights consistent with this
3329
+ License. However, in accepting such obligations, You may act only
3330
+ on Your own behalf and on Your sole responsibility, not on behalf
3331
+ of any other Contributor, and only if You agree to indemnify,
3332
+ defend, and hold each Contributor harmless for any liability
3333
+ incurred by, or claims asserted against, such Contributor by reason
3334
+ of your accepting any such warranty or additional liability.
3335
+
3336
+ END OF TERMS AND CONDITIONS
3337
+
3338
+ APPENDIX: How to apply the Apache License to your work.
3339
+
3340
+ To apply the Apache License to your work, attach the following
3341
+ boilerplate notice, with the fields enclosed by brackets "[]"
3342
+ replaced with your own identifying information. (Don't include
3343
+ the brackets!) The text should be enclosed in the appropriate
3344
+ comment syntax for the file format. We also recommend that a
3345
+ file or class name and description of purpose be included on the
3346
+ same "printed page" as the copyright notice for easier
3347
+ identification within third-party archives.
3348
+
3349
+ Copyright [yyyy] [name of copyright owner]
3350
+
3351
+ Licensed under the Apache License, Version 2.0 (the "License");
3352
+ you may not use this file except in compliance with the License.
3353
+ You may obtain a copy of the License at
3354
+
3355
+ http://www.apache.org/licenses/LICENSE-2.0
3356
+
3357
+ Unless required by applicable law or agreed to in writing, software
3358
+ distributed under the License is distributed on an "AS IS" BASIS,
3359
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3360
+ See the License for the specific language governing permissions and
3361
+ limitations under the License.`, "octicon": 'Octicons (MIT), by GitHub\nhttps://github.com/primer/octicons/\nLicence: https://github.com/primer/octicons/blob/main/LICENSE\n\nMIT License\n\nCopyright (c) 2026 GitHub Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.', "simple-icons": "Simple Icons (CC0-1.0), by Simple Icons Collaborators\nhttps://github.com/simple-icons/simple-icons\nLicence: https://github.com/simple-icons/simple-icons/blob/develop/LICENSE.md" };
3362
+
3363
+ // src/core/icon-notices.ts
3364
+ var ICON_SET_NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;
3365
+ function iconSetNotices(sets) {
3366
+ const more = [...new Set(sets)].filter((s) => s !== "lucide" && s !== "mdi" && ICON_SET_NAME.test(s)).sort();
3367
+ if (!more.length) return void 0;
3368
+ const lines = more.map((set) => ICON_SET_NOTICES[set] ?? `Iconify set "${set}": its licence is in @iconify-json/${set}/info.json`);
3369
+ return `Icons in the pages from other Iconify sets (@dogsbay/icons)
3370
+
3371
+ ${lines.join("\n\n")}
3372
+ `;
3373
+ }
3374
+
3118
3375
  // src/core/taxonomy.ts
3119
3376
  import { parseMeta } from "@dogsbay/types";
3120
3377
  function safeSegment(s) {
@@ -3734,7 +3991,7 @@ async function build(input, hooks = {}) {
3734
3991
  phase.init = now() - t0;
3735
3992
  let pages = 0;
3736
3993
  let assets = 0;
3737
- 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(), homeForward: /* @__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) };
3994
+ 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(), homeForward: /* @__PURE__ */ new Map(), usedMath: false, mathUnits: /* @__PURE__ */ new Set(), 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) };
3738
3995
  for (const w of cfg.chrome.warnings) report("config", `[config] ${w}`);
3739
3996
  state.logo = await emitLogo(cfg, input, emit, report);
3740
3997
  for (const r of cfg.redirects) {
@@ -3752,6 +4009,17 @@ async function build(input, hooks = {}) {
3752
4009
  stub: /\/\s*$/.test(r.from) ? `${from}/index.html` : void 0
3753
4010
  });
3754
4011
  }
4012
+ const stored = storedManifests(input.manifests, allUnits, units, report);
4013
+ input.manifests?.splice(0);
4014
+ const configRedirects = state.redirects.length;
4015
+ const unitRedirects = /* @__PURE__ */ new Map();
4016
+ for (const [prefix, m] of stored) {
4017
+ unitRedirects.set(prefix, m.redirectInputs);
4018
+ for (const [p, o] of m.pageOut) state.pageOut.set(p, o);
4019
+ state.sitemaps.set(prefix, m.sitemaps);
4020
+ if (m.homeForward !== void 0) state.homeForward.set(prefix, m.homeForward);
4021
+ }
4022
+ const unitRecords = /* @__PURE__ */ new Map();
3755
4023
  let frontMatterOf = frontMatterCache(readText);
3756
4024
  state.canonical = await defaultTables(cfg, allUnits, units, input.files, readText, (m) => report("canonical", m), frontMatterOf);
3757
4025
  if (siteBase(cfg)) state.languages = await languageTables(cfg, allUnits, units, input.files, readText, frontMatterOf);
@@ -3761,6 +4029,7 @@ async function build(input, hooks = {}) {
3761
4029
  hooks.context?.({ cfg, allUnits, input, highlighter, emit, warnings, problems, report, phase, theme, state, diagnostics: hooks.diagnostics, dataLines: hooks.dataLines });
3762
4030
  for (const unit of units) {
3763
4031
  classes = new Set(CLIENT_CANDIDATES);
4032
+ const before = { redirects: state.redirects.length, pageOut: state.pageOut.size, written: state.written.size };
3764
4033
  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 });
3765
4034
  hooks.unitBuilt?.(unit, r, classes);
3766
4035
  pages += r.pages;
@@ -3772,15 +4041,21 @@ async function build(input, hooks = {}) {
3772
4041
  report("css", `[css] could not build the stylesheet for ${unit.prefix || "/"}: ${e.message}`, { unit: unit.prefix || "/" });
3773
4042
  }
3774
4043
  phase.css = (phase.css ?? 0) + now() - tCss;
4044
+ unitRedirects.set(unit.prefix, state.redirects.slice(before.redirects));
4045
+ unitRecords.set(unit.prefix, { pageOut: [...state.pageOut].slice(before.pageOut), files: [...state.written].slice(before.written), iconSets: input.icons?.sets?.() ?? [] });
3775
4046
  }
4047
+ state.redirects = [...state.redirects.slice(0, configRedirects), ...allUnits.flatMap((u) => unitRedirects.get(u.prefix) ?? [])];
4048
+ await emit(`_dogsbay/assets/${CLIENT_FILE}`, CLIENT_JS);
3776
4049
  await emit("_dogsbay/assets/dogsbay.js", CLIENT_JS);
3777
- if (state.usedMath) {
4050
+ const storedUnits = [...stored.values()];
4051
+ const anyMath = state.usedMath || storedUnits.some((m) => m.math);
4052
+ if (anyMath) {
3778
4053
  const { KATEX_CSS, KATEX_FONTS } = await import("./chunks/katex-assets-7BGR6WCR.mjs");
3779
4054
  await emit("_dogsbay/assets/katex/katex.min.css", KATEX_CSS);
3780
4055
  for (const [f, b64] of Object.entries(KATEX_FONTS)) await emit(`_dogsbay/assets/katex/fonts/${f}`, base64Bytes(b64));
3781
4056
  }
3782
- const iconNotices = input.icons?.notices?.();
3783
- await emit("_dogsbay/assets/THIRD-PARTY-NOTICES.txt", [SITE_NOTICES, state.usedMath && KATEX_NOTICE, iconNotices].filter(Boolean).join("\n"));
4057
+ const iconNotices = iconSetNotices([...input.icons?.sets?.() ?? [], ...storedUnits.flatMap((m) => m.iconSets ?? [])]);
4058
+ await emit("_dogsbay/assets/THIRD-PARTY-NOTICES.txt", [SITE_NOTICES, anyMath && KATEX_NOTICE, iconNotices].filter(Boolean).join("\n"));
3784
4059
  await emit("_dogsbay/assets/favicon.svg", `${FAVICON_SVG}
3785
4060
  `);
3786
4061
  await emit(
@@ -3812,11 +4087,14 @@ async function build(input, hooks = {}) {
3812
4087
  if (!input.untrusted) state.hostFiles.set(n, c);
3813
4088
  else if (fromStatic !== void 0) report("untrusted", `[untrusted] ${cfg.staticDir}/${n} is not used: an untrusted build takes no host configuration`);
3814
4089
  }
4090
+ if (rootUnit && !units.includes(rootUnit)) {
4091
+ for (const n of SITE_FILES) if (input.files.has(sourceRoot(rootUnit.source) + n)) state.supplied.add(n);
4092
+ }
3815
4093
  if (state.staticFiles?.has("robots.txt")) {
3816
4094
  if (state.supplied.has("robots.txt")) throw new ConfigError(`${cfg.staticDir}/robots.txt and the root content's robots.txt are both the site's robots.txt: keep the one in content.static (${cfg.staticDir})`);
3817
4095
  state.supplied.add("robots.txt");
3818
4096
  }
3819
- await emitSiteFiles(cfg, allUnits, units, def, state, emit, report, input.onRedirects);
4097
+ const rules = await emitSiteFiles(cfg, allUnits, units, stored, def, state, emit, report, input.onRedirects);
3820
4098
  phase.site = (phase.site ?? 0) + now() - tSite;
3821
4099
  const homeOf = (fromDir, unitPrefix, fallback) => {
3822
4100
  const page = state.homeForward.get(unitPrefix);
@@ -3834,8 +4112,30 @@ async function build(input, hooks = {}) {
3834
4112
  if (root && u.version && u.version === cfg.defaultVersion && u.prefix !== root) await emit(`${root}/index.html`, redirectPage(homeOf(root, u.prefix, `${u.version}/index.html`), cfg.name));
3835
4113
  }
3836
4114
  }
3837
- if (state.staticFiles && (units.length === allUnits.length || rootUnit && units.includes(rootUnit))) await emitStatic(cfg, state, allUnits, input.untrusted === true, emit, report);
3838
- state.stubs = await writeTranslationStubs(cfg, allUnits, units, translations, state, emit);
4115
+ if (state.staticFiles && (units.length === allUnits.length || units.length === 0 || rootUnit && units.includes(rootUnit))) await emitStatic(cfg, state, allUnits, input.untrusted === true, emit, report);
4116
+ state.stubs = await writeTranslationStubs(cfg, allUnits, units, translations, state, emit, void 0, new Set(stored.keys()));
4117
+ if (allUnits.length > 1 && !hooks.lazy && !hooks.keep) {
4118
+ for (const u of units) {
4119
+ const rec = unitRecords.get(u.prefix);
4120
+ const own = rules.filter((r) => unitOfPath(allUnits, r.from) === u);
4121
+ const ssg = {
4122
+ prefix: u.prefix,
4123
+ redirectInputs: unitRedirects.get(u.prefix) ?? [],
4124
+ pageOut: rec.pageOut,
4125
+ sitemaps: state.sitemaps.get(u.prefix) ?? [],
4126
+ ...state.homeForward.has(u.prefix) ? { homeForward: state.homeForward.get(u.prefix) } : {},
4127
+ // Its redirect stubs and missing-translation stubs are written after the units, in its folder.
4128
+ files: [.../* @__PURE__ */ new Set([...rec.files, ...own.map(stubFile), ...state.stubs.filter((f) => unitOfPath(allUnits, f) === u)])],
4129
+ client: CLIENT_FILE,
4130
+ builder: SSG_VERSION,
4131
+ ...state.mathUnits.has(u.prefix) ? { math: true } : {},
4132
+ ...rec.iconSets.length ? { iconSets: rec.iconSets } : {}
4133
+ };
4134
+ const served = own.map((r) => siteRedirect(r, cfg)).map((r) => [r.from, r.to, r.status]);
4135
+ await emit(manifestPath(u), `${JSON.stringify(makeManifest(u, ssg, served))}
4136
+ `);
4137
+ }
4138
+ }
3839
4139
  const spent = state.templateBudget.problem();
3840
4140
  if (spent) report("template", spent);
3841
4141
  return {
@@ -3938,7 +4238,7 @@ async function emitStatic(cfg, state, units, untrusted, emit, report) {
3938
4238
  const label = `content.static (${cfg.staticDir})`;
3939
4239
  const lower = /* @__PURE__ */ new Map();
3940
4240
  const dirs = /* @__PURE__ */ new Map();
3941
- for (const w of state.written) {
4241
+ for (const w of /* @__PURE__ */ new Set([...state.written, ...state.pageOut.values()])) {
3942
4242
  lower.set(w.toLowerCase(), w);
3943
4243
  for (let i = w.indexOf("/"); i > 0; i = w.indexOf("/", i + 1)) dirs.set(w.slice(0, i).toLowerCase(), w);
3944
4244
  }
@@ -3982,7 +4282,7 @@ async function emitStatic(cfg, state, units, untrusted, emit, report) {
3982
4282
  if (plan.robots) for (const w of staticRobotsWarnings(await readText(files.get("robots.txt")), label)) report("static", `[static] ${w}`);
3983
4283
  for (const p of plan.copy) await emit(p, files.get(p));
3984
4284
  }
3985
- async function emitSiteFiles(cfg, all, built, def, state, emit, report, onRedirects) {
4285
+ async function emitSiteFiles(cfg, all, built, stored, def, state, emit, report, onRedirects) {
3986
4286
  const defPrefix = def?.prefix ?? "";
3987
4287
  const put = async (p, content) => {
3988
4288
  if (state.supplied.has(p)) {
@@ -4005,18 +4305,71 @@ async function emitSiteFiles(cfg, all, built, def, state, emit, report, onRedire
4005
4305
  } else if (cfg.url) {
4006
4306
  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`);
4007
4307
  }
4008
- if (!state.redirects.length) return merged("_redirects", void 0, mergeRedirects);
4308
+ if (!state.redirects.length) {
4309
+ await merged("_redirects", void 0, mergeRedirects);
4310
+ return [];
4311
+ }
4312
+ const unitOf2 = (p) => unitOfPath(all, p);
4009
4313
  const known = (p) => {
4010
- const u = unitOfPath(all, p);
4011
- return !!u && built.includes(u);
4314
+ const u = unitOf2(p);
4315
+ return !!u && (built.includes(u) || stored.has(u.prefix));
4012
4316
  };
4013
4317
  const inputs = state.redirects.filter((r) => known(r.from));
4014
4318
  const { rules, warnings: w } = collectRedirects(inputs, (p) => state.pageOut.get(p), known);
4319
+ const builtFrom = (r) => built.includes(unitOf2(r.from));
4015
4320
  for (const m of w) report("redirects", `[redirects] ${m}`);
4016
- onRedirects?.(rules);
4017
- for (const r of rules) await emit(stubFile(r), renderRedirectPage(r, cfg));
4321
+ onRedirects?.(rules.filter(builtFrom));
4322
+ const servedBefore = (r) => stored.get(unitOf2(r.from).prefix)?.redirects.get(siteRedirect(r, cfg).from);
4323
+ for (const r of rules) if (builtFrom(r) || servedBefore(r) !== siteRedirect(r, cfg).to) await emit(stubFile(r), renderRedirectPage(r, cfg));
4018
4324
  const ownRedirects = state.hostFiles.has("_redirects");
4019
4325
  await merged("_redirects", (cloudflare || ownRedirects) && rules.length ? renderRedirectsFile(rules, cfg) : void 0, mergeRedirects);
4326
+ return rules;
4327
+ }
4328
+ var siteRedirect = (r, cfg) => servedRedirect(r, { ...cfg, basePath: "" });
4329
+ function storedManifests(given, all, built, report) {
4330
+ const out = /* @__PURE__ */ new Map();
4331
+ if (!given) return out;
4332
+ for (const { path, text } of given) {
4333
+ const { manifest: m, problem } = parseManifest(text);
4334
+ if (!m) {
4335
+ report("units", `[units] ${path}: ${problem}; ignored`);
4336
+ continue;
4337
+ }
4338
+ const key = unitKey({ product: m.product ?? void 0, locale: m.locale ?? void 0, version: m.version ?? void 0 });
4339
+ const u = all.find((x) => unitKey(x) === key);
4340
+ if (!u) {
4341
+ report("units", `[units] ${path} is for ${key}, which the config no longer builds; ignored (remove its folder, ${m.ssg.prefix || "the site root"}, and the manifest)`);
4342
+ continue;
4343
+ }
4344
+ if (path !== manifestPath(u)) {
4345
+ report("units", `[units] ${path} is for ${key}, whose manifest is ${manifestPath(u)}; ignored`);
4346
+ continue;
4347
+ }
4348
+ if (u.prefix !== m.ssg.prefix) {
4349
+ report("units", `[units] ${path}: ${key} was built at ${m.ssg.prefix || "the site root"} and is now at ${u.prefix || "the site root"}; ignored until it is built again`);
4350
+ continue;
4351
+ }
4352
+ if (built.includes(u)) continue;
4353
+ const clean = (x) => !/[\s\u0000-\u001f\u007f]/.test(x) && !x.split("/").some((p) => p === ".." || p === ".");
4354
+ const mine = (p) => clean(p) && unitOfPath(all, p) === u;
4355
+ const redirectInputs = m.ssg.redirectInputs.filter((r) => mine(r.from) && (r.stub === void 0 || mine(r.stub)) && clean(r.to) && (r.suffix === void 0 || clean(r.suffix)));
4356
+ const pageOut = m.ssg.pageOut.filter(([p, o]) => mine(p) && mine(o));
4357
+ const sitemaps = m.ssg.sitemaps.filter(mine);
4358
+ const dropped = m.ssg.redirectInputs.length - redirectInputs.length + m.ssg.pageOut.length - pageOut.length + m.ssg.sitemaps.length - sitemaps.length;
4359
+ if (dropped) report("units", `[units] ${path}: ${dropped} entr${dropped === 1 ? "y" : "ies"} outside ${u.prefix || "the root unit"} left out`);
4360
+ out.set(u.prefix, {
4361
+ redirectInputs,
4362
+ pageOut,
4363
+ sitemaps,
4364
+ homeForward: m.ssg.homeForward !== void 0 && mine(m.ssg.homeForward) ? m.ssg.homeForward : void 0,
4365
+ math: m.ssg.math,
4366
+ iconSets: m.ssg.iconSets,
4367
+ redirects: new Map(m.redirects.map(([f, t]) => [f, t]))
4368
+ });
4369
+ }
4370
+ const missing = all.filter((u) => !built.includes(u) && !out.has(u.prefix));
4371
+ if (missing.length) report("units", `[units] no manifest for ${missing.map((u) => u.prefix || "the root unit").join(", ")}: not built yet, so the site-wide files leave out their redirects`);
4372
+ return out;
4020
4373
  }
4021
4374
  function defaultUnit(units, cfg) {
4022
4375
  if (cfg.products) {
@@ -4336,6 +4689,10 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
4336
4689
  blog?.add(slug, infos.get(slug).title, fm);
4337
4690
  if (mode.keep || mode.lazy) frontMatter.set(p, memo2 ? memo2.fmKey ??= fmKey(await source()) : fmKey(await source()));
4338
4691
  } else if (!SKIP_FILES.has(memberRel(p)) && copiedPath(p)) {
4692
+ if (p.split("/").some((seg) => seg.toLowerCase() === "_dogsbay")) {
4693
+ warn("site", `${p} is inside a folder named _dogsbay, the build's own; not copied`, { file: abs(p) });
4694
+ continue;
4695
+ }
4339
4696
  const verdict = await assetVerdict(p, c, input.untrusted === true, sanitizing);
4340
4697
  if (verdict === "untrusted") untrustedFiles.push(p);
4341
4698
  else if (verdict === "active") activeFiles.push(p);
@@ -4863,7 +5220,10 @@ ${marker}${title}${marker}`, path, attributesOf(path), includesFor(path, read2),
4863
5220
  for (const u of ctx.unsafeUrls ?? []) warnPage(info, "unsafe-url", `refused the unsafe URL "${u}"`);
4864
5221
  for (const m of removed) warnPage(info, "html", m);
4865
5222
  for (const name of ctx.missingIcons ?? []) warnPage(info, "icon", `unknown icon "${name}"${input.icons ? "" : " (only the built-in icons are available in this build)"}`);
4866
- if (ctx.usedMath) state.usedMath = true;
5223
+ if (ctx.usedMath) {
5224
+ state.usedMath = true;
5225
+ state.mathUnits.add(unit.prefix);
5226
+ }
4867
5227
  for (const b of resolver.broken) brokenCounts.has(b) ? brokenCounts.set(b, brokenCounts.get(b) + 1) : brokenCounts.set(detach(b), 1);
4868
5228
  phase.render = (phase.render ?? 0) + now() - t;
4869
5229
  t = now();
@@ -5580,7 +5940,7 @@ function versionBanner(unit, all, cfg, out, siteRoot, t) {
5580
5940
  if (!latest) return void 0;
5581
5941
  return esc3(t("oldVersion")) + link(latest);
5582
5942
  }
5583
- async function writeTranslationStubs(cfg, all, built, tables, state, emit, exists = () => false) {
5943
+ async function writeTranslationStubs(cfg, all, built, tables, state, emit, exists = () => false, stored = /* @__PURE__ */ new Set()) {
5584
5944
  const def = cfg.defaultLocale;
5585
5945
  if (!def || !tables.size) return [];
5586
5946
  const unitOf2 = (locale, version) => all.find((u) => u.locale === locale && u.version === version && !u.product);
@@ -5599,10 +5959,11 @@ async function writeTranslationStubs(cfg, all, built, tables, state, emit, exist
5599
5959
  const home = unitOf2(def, m.version);
5600
5960
  const out = tables.get(home.prefix).pages.get(m.page);
5601
5961
  const target = unitOf2(m.locale, m.version);
5602
- if (!built.includes(target ?? home)) continue;
5603
- const to = join(home.prefix, out);
5604
- if (built.includes(home) && !pageFiles.has(to)) continue;
5605
5962
  const file = join(target ? target.prefix : m.locale + home.prefix.slice(def.length), out);
5963
+ const folder = target ? void 0 : unitOfPath(all, file);
5964
+ if (!built.includes(target ?? home) && !(folder && built.includes(folder) && stored.has(home.prefix))) continue;
5965
+ const to = join(home.prefix, out);
5966
+ if ((built.includes(home) || stored.has(home.prefix)) && !pageFiles.has(to)) continue;
5606
5967
  if (state.written.has(file) || pageFiles.has(file) || exists(file)) continue;
5607
5968
  await emit(file, redirectPage(hrefBetween(file, to, cfg.linkStyle), cfg.name));
5608
5969
  written.push(file);
@@ -34,11 +34,11 @@ export type SinkContent = string | Uint8Array | FileRef | TextStream;
34
34
  /** A built file held in memory (no sink). */
35
35
  export type OutputContent = string | Uint8Array;
36
36
  /**
37
- * Icon name → SVG markup or an emoji (undefined when unknown). `notices`, when the host has it,
38
- * returns the attribution for the icon sets the pages drew from, for THIRD-PARTY-NOTICES.txt.
37
+ * Icon name → SVG markup or an emoji (undefined when unknown). `sets`, when the host has it, returns
38
+ * the Iconify sets the pages drew from, whose attribution goes in THIRD-PARTY-NOTICES.txt.
39
39
  */
40
40
  export type IconResolver = ((name: string) => string | undefined) & {
41
- notices?: () => string | undefined;
41
+ sets?: () => string[];
42
42
  };
43
43
  export interface BuildInput {
44
44
  /** Project files keyed by path relative to the project root (where the config lives). */
@@ -47,6 +47,17 @@ export interface BuildInput {
47
47
  config?: string | SiteConfig;
48
48
  /** Build only the units with these prefixes (a Worker builds one unit per request). */
49
49
  units?: string[];
50
+ /**
51
+ * The unit manifests of the site this build updates (`_dogsbay/units/…`, plans/unit-publishing.md):
52
+ * a unit not built here is represented by its manifest in the site-wide files (`_redirects`, the
53
+ * sitemap index, the root forwards), so a one-unit build writes them as a full build would.
54
+ * `path` names the file in warnings. The build empties the array once it has read them, so their
55
+ * text is not held through the build (the Worker's memory).
56
+ */
57
+ manifests?: Array<{
58
+ path: string;
59
+ text: string;
60
+ }>;
50
61
  /**
51
62
  * content.html when the project does not set it: "keep" (the default) or "sanitize"
52
63
  * (the Worker's, where projects come from anyone with the token and share one origin).
@@ -236,6 +247,8 @@ export interface SiteState {
236
247
  };
237
248
  /** A page rendered math, so the KaTeX stylesheet and fonts are written. */
238
249
  usedMath: boolean;
250
+ /** The units (prefixes) a page of which rendered math, for their manifests. */
251
+ mathUnits: Set<string>;
239
252
  /** Rendered diagrams, shared by the units of the build. */
240
253
  diagrams?: DiagramStore;
241
254
  /** The default version's pages and redirects per language, for older versions' canonical links (src/core/canonical.ts). */
@@ -479,4 +492,6 @@ export interface LoadedTheme {
479
492
  * unit was built here, only to a page it wrote (the nav may leave pages out). In another
480
493
  * language a redirect source holds its URL, so it counts as present. Returns the stubs written.
481
494
  */
482
- export declare function writeTranslationStubs(cfg: SiteConfig, all: BuildUnit[], built: BuildUnit[], tables: Map<string, DefaultTable>, state: Pick<SiteState, "written" | "pageOut">, emit: (path: string, c: FileContent) => Promise<void>, exists?: (path: string) => boolean): Promise<string[]>;
495
+ export declare function writeTranslationStubs(cfg: SiteConfig, all: BuildUnit[], built: BuildUnit[], tables: Map<string, DefaultTable>, state: Pick<SiteState, "written" | "pageOut">, emit: (path: string, c: FileContent) => Promise<void>, exists?: (path: string) => boolean,
496
+ /** Units not built here whose pages `state.pageOut` holds (from their manifests). */
497
+ stored?: ReadonlySet<string>): Promise<string[]>;
@@ -0,0 +1,9 @@
1
+ /** An Iconify set name as a unit manifest may record it (the `icon-notices` table's keys are of this shape). */
2
+ export declare const ICON_SET_NAME: RegExp;
3
+ /**
4
+ * THIRD-PARTY-NOTICES.txt's section for the Iconify sets the pages drew from (`sets`, prefixes), or
5
+ * undefined when there is none to list. Lucide and MDI are listed with render-html's built-in icons in
6
+ * every site already. Pure, so a one-unit build lists the sets its stored units recorded too
7
+ * (plans/unit-publishing.md).
8
+ */
9
+ export declare function iconSetNotices(sets: Iterable<string>): string | undefined;
@@ -117,5 +117,11 @@ export declare function splitSuffix(raw: string): {
117
117
  export declare function stubFile(rule: RedirectRule): string;
118
118
  /** A meta-refresh page, as `renderMetaRefreshPage`, with a relative target so it works from disk. */
119
119
  export declare function renderRedirectPage(rule: RedirectRule, cfg: SiteConfig): string;
120
+ /** A rule as the site serves it: `from` and `to` with the base path, `to` as its page is served. */
121
+ export declare function servedRedirect(r: RedirectRule, cfg: SiteConfig): {
122
+ from: string;
123
+ to: string;
124
+ status: number;
125
+ };
120
126
  /** _redirects (Cloudflare), as `renderRedirectsFile`, with served paths. */
121
127
  export declare function renderRedirectsFile(rules: RedirectRule[], cfg: SiteConfig): string;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * A manifest per unit (plans/unit-publishing.md): what each unit's build leaves for the site-wide
3
+ * files, so one product, version or language can be built and published on its own and the
4
+ * `_redirects`, sitemap index and root forwards still cover every unit. The format is
5
+ * @dogsbay/site-files' `UnitManifest` (shared with the Dogsbay CLI), with our own `ssg` section.
6
+ * Written at `_dogsbay/units/<unitKeyString>.json` on sites with two or more units. Pure: the CLI
7
+ * and the Worker read the files and hand them to `build()` as `BuildInput.manifests`.
8
+ */
9
+ import { type UnitManifest } from "@dogsbay/site-files";
10
+ import type { BuildUnit } from "./config.js";
11
+ import type { RedirectInput } from "./site-files.js";
12
+ /** What a unit's build records for the next one-unit build or assemble. */
13
+ export interface SsgUnitData {
14
+ /** The unit's folder in the site ("" at the root). */
15
+ prefix: string;
16
+ /** Its redirects before they are resolved (front matter and aliases; config redirects come from the config). */
17
+ redirectInputs: RedirectInput[];
18
+ /** `[site path, output file]` of its pages, so other units' redirects resolve against them. */
19
+ pageOut: Array<[string, string]>;
20
+ /** Its sitemap files, for the sitemap index. */
21
+ sitemaps: string[];
22
+ /** The page its home forwards to, when it has no home page. */
23
+ homeForward?: string;
24
+ /** Every file it wrote, so the next publish of the unit can remove the ones it no longer writes. */
25
+ files: string[];
26
+ /** The client file its pages load (`_dogsbay/assets/<client>`). */
27
+ client: string;
28
+ /** The dogsbay-ssg version that built it. */
29
+ builder: string;
30
+ /** A page rendered math: the site keeps KaTeX's files and its notice. */
31
+ math?: boolean;
32
+ /** The Iconify sets its pages drew from, for THIRD-PARTY-NOTICES.txt (may list sets an earlier unit of the same build used). */
33
+ iconSets?: string[];
34
+ }
35
+ export type SsgUnitManifest = UnitManifest & {
36
+ ssg: SsgUnitData;
37
+ };
38
+ /** The unit's key, as site-files names a unit (`calico/_/3.32`). */
39
+ export declare function unitKey(u: Pick<BuildUnit, "product" | "locale" | "version">): string;
40
+ /** Where a unit's manifest is written, from the site root. */
41
+ export declare function manifestPath(u: Pick<BuildUnit, "product" | "locale" | "version">): string;
42
+ /** The folder the manifests sit in, from the site root. */
43
+ export declare const MANIFEST_DIR = "_dogsbay/units/";
44
+ export declare function makeManifest(u: BuildUnit, ssg: SsgUnitData, redirects: Array<[string, string, number]>): SsgUnitManifest;
45
+ /**
46
+ * A manifest read back (JSON text or a parsed value), checked: undefined, with a reason, when it is
47
+ * not one this build can use (another format, or written by something other than dogsbay-ssg).
48
+ */
49
+ export declare function parseManifest(raw: unknown): {
50
+ manifest?: SsgUnitManifest;
51
+ problem?: string;
52
+ };
53
+ /** The hashed client files (`dogsbay-<hash>.js`, in `_dogsbay/assets/`) an update removes when no unit loads them. */
54
+ export declare const CLIENT_PATTERN: RegExp;
55
+ /** What removing stale files needs of a manifest. */
56
+ export interface UnitFiles {
57
+ prefix: string;
58
+ files: string[];
59
+ client: string;
60
+ }
61
+ /** A manifest's `UnitFiles`, or null when the text is not a manifest this version can read. */
62
+ export declare function unitFilesOf(text: string): UnitFiles | null;
63
+ /**
64
+ * After a one-unit update, the files to remove (site paths). `previous` and `now` are the site's
65
+ * manifests before and after the build, by path (null: unreadable); a unit counts as rebuilt when this
66
+ * build wrote its manifest (`written` holds every path the build wrote).
67
+ * - A rebuilt unit's files its previous manifest listed and its new one does not (pages deleted or
68
+ * moved), unless this build wrote them, or they are not in that unit's own folder: the unit whose
69
+ * prefix is the longest to hold the path (a version at the root holds the others' folders), so a
70
+ * manifest can only ever remove files of its own unit.
71
+ * - The hashed clients in `assets` (file names) no manifest loads any more; none when a manifest
72
+ * cannot be read, since its pages may load one.
73
+ */
74
+ export declare function staleUnitFiles(previous: ReadonlyMap<string, UnitFiles | null>, now: ReadonlyMap<string, UnitFiles | null>, written: ReadonlySet<string>, assets: readonly string[]): string[];
@@ -1,4 +1,11 @@
1
1
  export declare const CLIENT_JS: string;
2
+ /**
3
+ * The name the pages load the client by, from its content (_dogsbay/assets/<CLIENT_FILE>): a unit built
4
+ * by another version of the client keeps loading its own (plans/unit-publishing.md).
5
+ */
6
+ export declare const CLIENT_FILE: string;
7
+ /** This package's version, recorded in each unit's manifest. */
8
+ export declare const SSG_VERSION: string;
2
9
  export declare const THEME_TEMPLATES: Record<string, string>;
3
10
  export declare const CLIENT_CANDIDATES: string[];
4
11
  /** _dogsbay/assets/THIRD-PARTY-NOTICES.txt: the client's packages and Tailwind (scripts/notices.mjs). */
@@ -0,0 +1,2 @@
1
+ /** Each Iconify set's attribution and licence (scripts/notices.mjs iconSetNotice), by prefix. */
2
+ export declare const ICON_SET_NOTICES: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/ssg",
3
- "version": "0.2.0-beta.151",
3
+ "version": "0.2.0-beta.151.1",
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",