wiki-formant 0.81.0 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/check-pages.mjs +36 -1
- package/package.json +1 -1
package/bin/check-pages.mjs
CHANGED
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
// URLs come from /sitemap.xml, so a route the sitemap omits is not checked —
|
|
28
28
|
// which is the right default, since an unlisted route is one nobody asked a
|
|
29
29
|
// crawler to read.
|
|
30
|
+
//
|
|
31
|
+
// Once per site it also checks the icon. miow.me changed its icon in October
|
|
32
|
+
// 2026 and every address of it 404'd at /favicon.ico, the one place a client
|
|
33
|
+
// that never reads the page's <link rel="icon"> looks; so /favicon.ico must
|
|
34
|
+
// answer an image, and the same image the home page declares.
|
|
30
35
|
import { outlineIssues } from '../dist/headings.js';
|
|
31
36
|
import { readSitemap } from '../dist/indexnow.js';
|
|
32
37
|
import { decodeEntities } from '../dist/markdown.js';
|
|
@@ -125,6 +130,36 @@ for (const url of checked) {
|
|
|
125
130
|
if (descriptionOf(page.body) === null) faults.push({ url, fault: 'noDescription', detail: '' });
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
// The icon, once per site: what the home page declares (a page that declares
|
|
134
|
+
// none is shown /favicon.ico) against what /favicon.ico answers, redirects
|
|
135
|
+
// followed.
|
|
136
|
+
const iconHref = html => {
|
|
137
|
+
for (const tag of html.match(/<link\b[^>]*>/gi) ?? []) {
|
|
138
|
+
if (/\brel=["'](?:shortcut )?icon["']/i.test(tag)) return decodeEntities(tag.match(/\bhref=["']([^"']+)["']/i)?.[1] ?? '');
|
|
139
|
+
}
|
|
140
|
+
return '';
|
|
141
|
+
};
|
|
142
|
+
const image = async url => {
|
|
143
|
+
try {
|
|
144
|
+
const res = await fetch(url, { headers: { 'user-agent': UA } });
|
|
145
|
+
const type = res.headers.get('content-type') ?? '';
|
|
146
|
+
return res.ok && /^image\//i.test(type) ? Buffer.from(await res.arrayBuffer()) : `${res.status} ${type}`.trim();
|
|
147
|
+
} catch (e) {
|
|
148
|
+
return e.message;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
{
|
|
152
|
+
const home = await get(`${site}/`).catch(() => ({ body: '' }));
|
|
153
|
+
const declared = new URL(iconHref(home.body) || '/favicon.ico', `${site}/`).href;
|
|
154
|
+
const favicon = await image(`${site}/favicon.ico`);
|
|
155
|
+
const shown = declared === `${site}/favicon.ico` ? favicon : await image(declared);
|
|
156
|
+
if (!Buffer.isBuffer(favicon)) faults.push({ url: `${site}/favicon.ico`, fault: 'noFavicon', detail: favicon });
|
|
157
|
+
if (!Buffer.isBuffer(shown)) faults.push({ url: declared, fault: 'iconDead', detail: shown });
|
|
158
|
+
else if (Buffer.isBuffer(favicon) && !favicon.equals(shown)) {
|
|
159
|
+
faults.push({ url: `${site}/favicon.ico`, fault: 'faviconStale', detail: `not the icon the home page declares, ${declared.replace(site, '')}` });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
128
163
|
// Reported once per title rather than once per page, or a shared title on forty
|
|
129
164
|
// routes drowns everything else.
|
|
130
165
|
for (const [title, sharing] of titles) {
|
|
@@ -149,7 +184,7 @@ if (JSON_OUT) {
|
|
|
149
184
|
const byFault = new Map();
|
|
150
185
|
for (const f of faults) byFault.set(f.fault, [...(byFault.get(f.fault) ?? []), f]);
|
|
151
186
|
console.log(`check-pages: ${checked.length} of ${urls.length} sitemap URLs on ${site}\n`);
|
|
152
|
-
if (!faults.length) console.log(' clean — one h1 per page, no skipped levels, every title inside the budget.');
|
|
187
|
+
if (!faults.length) console.log(' clean — one h1 per page, no skipped levels, every title inside the budget, and /favicon.ico the icon the page declares.');
|
|
153
188
|
else if (!failing.length) console.log(' no failures; everything below is advisory.\n');
|
|
154
189
|
for (const [fault, list] of [...byFault].sort((a, b) => b[1].length - a[1].length)) {
|
|
155
190
|
console.log(` ${fault} (${list.length})${ADVISORY.has(fault) ? ' — advisory, does not fail' : ''}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-formant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.82.0",
|
|
4
4
|
"description": "The portable half of a wiki: derived taxonomy and facet controls, a version-negotiating MCP transport, markdown twins, block rendering, a rich-text editor engine, conditional-GET plumbing, and the headers and CSP every origin sends.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|