ax-audit 4.2.1 → 6.0.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/CHANGELOG.md +19 -0
- package/README.md +39 -4
- package/dist/baseline.js +1 -1
- package/dist/baseline.js.map +1 -1
- package/dist/check-ids.js +1 -1
- package/dist/check-ids.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +1 -85
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -135
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/license.d.ts +8 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +18 -0
- package/dist/license.js.map +1 -0
- package/dist/metadata.d.ts +6 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +205 -0
- package/dist/metadata.js.map +1 -0
- package/dist/orchestrator.d.ts +1 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +134 -56
- package/dist/orchestrator.js.map +1 -1
- package/dist/scorer.js +2 -2
- package/dist/scorer.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/api.md +14 -3
- package/docs/architecture.md +9 -98
- package/docs/ci.md +19 -0
- package/docs/cli.md +11 -0
- package/docs/faq.md +18 -0
- package/docs/getting-started.md +33 -1
- package/package.json +5 -5
- package/dist/checks/agent-access.d.ts +0 -33
- package/dist/checks/agent-access.d.ts.map +0 -1
- package/dist/checks/agent-access.js +0 -256
- package/dist/checks/agent-access.js.map +0 -1
- package/dist/checks/agent-card.d.ts +0 -37
- package/dist/checks/agent-card.d.ts.map +0 -1
- package/dist/checks/agent-card.js +0 -352
- package/dist/checks/agent-card.js.map +0 -1
- package/dist/checks/agent-operability.d.ts +0 -66
- package/dist/checks/agent-operability.d.ts.map +0 -1
- package/dist/checks/agent-operability.js +0 -383
- package/dist/checks/agent-operability.js.map +0 -1
- package/dist/checks/agent-skills.d.ts +0 -24
- package/dist/checks/agent-skills.d.ts.map +0 -1
- package/dist/checks/agent-skills.js +0 -316
- package/dist/checks/agent-skills.js.map +0 -1
- package/dist/checks/ai-catalog.d.ts +0 -28
- package/dist/checks/ai-catalog.d.ts.map +0 -1
- package/dist/checks/ai-catalog.js +0 -254
- package/dist/checks/ai-catalog.js.map +0 -1
- package/dist/checks/ai-directives.d.ts +0 -57
- package/dist/checks/ai-directives.d.ts.map +0 -1
- package/dist/checks/ai-directives.js +0 -263
- package/dist/checks/ai-directives.js.map +0 -1
- package/dist/checks/api-discovery.d.ts +0 -26
- package/dist/checks/api-discovery.d.ts.map +0 -1
- package/dist/checks/api-discovery.js +0 -432
- package/dist/checks/api-discovery.js.map +0 -1
- package/dist/checks/auth-discovery.d.ts +0 -28
- package/dist/checks/auth-discovery.d.ts.map +0 -1
- package/dist/checks/auth-discovery.js +0 -302
- package/dist/checks/auth-discovery.js.map +0 -1
- package/dist/checks/commerce-discovery.d.ts +0 -40
- package/dist/checks/commerce-discovery.d.ts.map +0 -1
- package/dist/checks/commerce-discovery.js +0 -295
- package/dist/checks/commerce-discovery.js.map +0 -1
- package/dist/checks/content-negotiation.d.ts +0 -4
- package/dist/checks/content-negotiation.d.ts.map +0 -1
- package/dist/checks/content-negotiation.js +0 -253
- package/dist/checks/content-negotiation.js.map +0 -1
- package/dist/checks/crawl-efficiency.d.ts +0 -16
- package/dist/checks/crawl-efficiency.d.ts.map +0 -1
- package/dist/checks/crawl-efficiency.js +0 -186
- package/dist/checks/crawl-efficiency.js.map +0 -1
- package/dist/checks/frontmatter.d.ts +0 -34
- package/dist/checks/frontmatter.d.ts.map +0 -1
- package/dist/checks/frontmatter.js +0 -100
- package/dist/checks/frontmatter.js.map +0 -1
- package/dist/checks/html-rendering.d.ts +0 -21
- package/dist/checks/html-rendering.d.ts.map +0 -1
- package/dist/checks/html-rendering.js +0 -221
- package/dist/checks/html-rendering.js.map +0 -1
- package/dist/checks/html-utils.d.ts +0 -66
- package/dist/checks/html-utils.d.ts.map +0 -1
- package/dist/checks/html-utils.js +0 -139
- package/dist/checks/html-utils.js.map +0 -1
- package/dist/checks/http-headers.d.ts +0 -11
- package/dist/checks/http-headers.d.ts.map +0 -1
- package/dist/checks/http-headers.js +0 -249
- package/dist/checks/http-headers.js.map +0 -1
- package/dist/checks/http-hygiene.d.ts +0 -26
- package/dist/checks/http-hygiene.d.ts.map +0 -1
- package/dist/checks/http-hygiene.js +0 -257
- package/dist/checks/http-hygiene.js.map +0 -1
- package/dist/checks/index.d.ts +0 -3
- package/dist/checks/index.d.ts.map +0 -1
- package/dist/checks/index.js +0 -55
- package/dist/checks/index.js.map +0 -1
- package/dist/checks/llms-txt.d.ts +0 -19
- package/dist/checks/llms-txt.d.ts.map +0 -1
- package/dist/checks/llms-txt.js +0 -281
- package/dist/checks/llms-txt.js.map +0 -1
- package/dist/checks/mcp-discovery.d.ts +0 -30
- package/dist/checks/mcp-discovery.d.ts.map +0 -1
- package/dist/checks/mcp-discovery.js +0 -523
- package/dist/checks/mcp-discovery.js.map +0 -1
- package/dist/checks/meta-tags.d.ts +0 -17
- package/dist/checks/meta-tags.d.ts.map +0 -1
- package/dist/checks/meta-tags.js +0 -188
- package/dist/checks/meta-tags.js.map +0 -1
- package/dist/checks/robots-parser.d.ts +0 -110
- package/dist/checks/robots-parser.d.ts.map +0 -1
- package/dist/checks/robots-parser.js +0 -277
- package/dist/checks/robots-parser.js.map +0 -1
- package/dist/checks/robots-txt.d.ts +0 -6
- package/dist/checks/robots-txt.d.ts.map +0 -1
- package/dist/checks/robots-txt.js +0 -367
- package/dist/checks/robots-txt.js.map +0 -1
- package/dist/checks/rsl.d.ts +0 -4
- package/dist/checks/rsl.d.ts.map +0 -1
- package/dist/checks/rsl.js +0 -242
- package/dist/checks/rsl.js.map +0 -1
- package/dist/checks/security-txt.d.ts +0 -4
- package/dist/checks/security-txt.d.ts.map +0 -1
- package/dist/checks/security-txt.js +0 -81
- package/dist/checks/security-txt.js.map +0 -1
- package/dist/checks/seo-basics.d.ts +0 -13
- package/dist/checks/seo-basics.d.ts.map +0 -1
- package/dist/checks/seo-basics.js +0 -221
- package/dist/checks/seo-basics.js.map +0 -1
- package/dist/checks/sitemap.d.ts +0 -12
- package/dist/checks/sitemap.d.ts.map +0 -1
- package/dist/checks/sitemap.js +0 -240
- package/dist/checks/sitemap.js.map +0 -1
- package/dist/checks/structured-data.d.ts +0 -4
- package/dist/checks/structured-data.d.ts.map +0 -1
- package/dist/checks/structured-data.js +0 -408
- package/dist/checks/structured-data.js.map +0 -1
- package/dist/checks/structured-fields.d.ts +0 -46
- package/dist/checks/structured-fields.d.ts.map +0 -1
- package/dist/checks/structured-fields.js +0 -112
- package/dist/checks/structured-fields.js.map +0 -1
- package/dist/checks/surface.d.ts +0 -59
- package/dist/checks/surface.d.ts.map +0 -1
- package/dist/checks/surface.js +0 -106
- package/dist/checks/surface.js.map +0 -1
- package/dist/checks/tls-https.d.ts +0 -13
- package/dist/checks/tls-https.d.ts.map +0 -1
- package/dist/checks/tls-https.js +0 -163
- package/dist/checks/tls-https.js.map +0 -1
- package/dist/checks/usage-policy.d.ts +0 -53
- package/dist/checks/usage-policy.d.ts.map +0 -1
- package/dist/checks/usage-policy.js +0 -339
- package/dist/checks/usage-policy.js.map +0 -1
- package/dist/checks/utils.d.ts +0 -40
- package/dist/checks/utils.d.ts.map +0 -1
- package/dist/checks/utils.js +0 -74
- package/dist/checks/utils.js.map +0 -1
- package/dist/checks/waf.d.ts +0 -75
- package/dist/checks/waf.d.ts.map +0 -1
- package/dist/checks/waf.js +0 -203
- package/dist/checks/waf.js.map +0 -1
- package/dist/checks/webmcp.d.ts +0 -55
- package/dist/checks/webmcp.d.ts.map +0 -1
- package/dist/checks/webmcp.js +0 -209
- package/dist/checks/webmcp.js.map +0 -1
- package/dist/checks/well-known.d.ts +0 -38
- package/dist/checks/well-known.d.ts.map +0 -1
- package/dist/checks/well-known.js +0 -202
- package/dist/checks/well-known.js.map +0 -1
- package/dist/fetcher.d.ts +0 -15
- package/dist/fetcher.d.ts.map +0 -1
- package/dist/fetcher.js +0 -127
- package/dist/fetcher.js.map +0 -1
- package/dist/guide-urls.d.ts +0 -2
- package/dist/guide-urls.d.ts.map +0 -1
- package/dist/guide-urls.js +0 -5
- package/dist/guide-urls.js.map +0 -1
- package/docs/roadmap.md +0 -367
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal YAML frontmatter reader.
|
|
3
|
-
*
|
|
4
|
-
* ax-audit has two runtime dependencies and will not grow a YAML parser for
|
|
5
|
-
* this. The documents it reads frontmatter from — SKILL.md, the Markdown
|
|
6
|
-
* mirrors Vercel and Mintlify serve — use a flat `key: value` block by
|
|
7
|
-
* convention, so a focused reader handles them exactly and reports anything
|
|
8
|
-
* more elaborate as unparsed rather than guessing at it.
|
|
9
|
-
*
|
|
10
|
-
* Supported: `key: value`, single- and double-quoted values, `#` comments on
|
|
11
|
-
* their own line or after a value, and inline flow sequences (`tags: [a, b]`).
|
|
12
|
-
* Nested mappings and block sequences are recognised and skipped, with their
|
|
13
|
-
* keys recorded in `skipped` so a caller can say what it could not read.
|
|
14
|
-
*/
|
|
15
|
-
/** Strip matching surrounding quotes and unescape the pairs YAML defines inside them. */
|
|
16
|
-
function unquote(value) {
|
|
17
|
-
const trimmed = value.trim();
|
|
18
|
-
if (trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
19
|
-
return trimmed.slice(1, -1).replace(/\\(["\\n])/g, (_, c) => (c === 'n' ? '\n' : c));
|
|
20
|
-
}
|
|
21
|
-
if (trimmed.length >= 2 && trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
22
|
-
return trimmed.slice(1, -1).replace(/''/g, "'");
|
|
23
|
-
}
|
|
24
|
-
return trimmed;
|
|
25
|
-
}
|
|
26
|
-
/** Remove a trailing `#` comment from an unquoted value. */
|
|
27
|
-
function stripComment(value) {
|
|
28
|
-
const trimmed = value.trim();
|
|
29
|
-
if (trimmed.startsWith('"') || trimmed.startsWith("'"))
|
|
30
|
-
return trimmed;
|
|
31
|
-
const hash = trimmed.search(/\s#/);
|
|
32
|
-
return hash === -1 ? trimmed : trimmed.slice(0, hash).trim();
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Parse a leading `---` delimited frontmatter block.
|
|
36
|
-
*
|
|
37
|
-
* A document with no block returns `present: false` and its whole text as the
|
|
38
|
-
* body, so callers can distinguish "no frontmatter" from "empty frontmatter".
|
|
39
|
-
*/
|
|
40
|
-
export function parseFrontmatter(document) {
|
|
41
|
-
const empty = { frontmatter: {}, lists: {}, skipped: [], body: document, present: false };
|
|
42
|
-
// Tolerate a BOM and leading blank lines before the opening delimiter.
|
|
43
|
-
const text = document.replace(/^\uFEFF/, '');
|
|
44
|
-
const opening = text.match(/^\s*---[ \t]*\r?\n/);
|
|
45
|
-
if (!opening)
|
|
46
|
-
return empty;
|
|
47
|
-
const rest = text.slice(opening[0].length);
|
|
48
|
-
const closing = rest.match(/^---[ \t]*(?:\r?\n|$)/m);
|
|
49
|
-
if (!closing || closing.index === undefined)
|
|
50
|
-
return empty;
|
|
51
|
-
const block = rest.slice(0, closing.index);
|
|
52
|
-
const body = rest.slice(closing.index + closing[0].length);
|
|
53
|
-
const frontmatter = {};
|
|
54
|
-
const lists = {};
|
|
55
|
-
const skipped = [];
|
|
56
|
-
const lines = block.split(/\r?\n/);
|
|
57
|
-
for (let i = 0; i < lines.length; i++) {
|
|
58
|
-
const line = lines[i];
|
|
59
|
-
if (line.trim() === '' || line.trim().startsWith('#'))
|
|
60
|
-
continue;
|
|
61
|
-
// Indented lines belong to a structure the previous key opened.
|
|
62
|
-
if (/^\s/.test(line))
|
|
63
|
-
continue;
|
|
64
|
-
const match = line.match(/^([A-Za-z0-9_.-]+)\s*:\s*(.*)$/);
|
|
65
|
-
if (!match)
|
|
66
|
-
continue;
|
|
67
|
-
const key = match[1].toLowerCase();
|
|
68
|
-
const raw = match[2];
|
|
69
|
-
if (raw.trim() === '') {
|
|
70
|
-
// A key with no inline value opens a nested mapping or block sequence.
|
|
71
|
-
const next = lines[i + 1] ?? '';
|
|
72
|
-
if (/^\s*-\s+/.test(next)) {
|
|
73
|
-
const items = [];
|
|
74
|
-
for (let j = i + 1; j < lines.length && /^\s*-\s+/.test(lines[j]); j++) {
|
|
75
|
-
items.push(unquote(stripComment(lines[j].replace(/^\s*-\s+/, ''))));
|
|
76
|
-
}
|
|
77
|
-
lists[key] = items;
|
|
78
|
-
}
|
|
79
|
-
else if (/^\s+\S/.test(next)) {
|
|
80
|
-
skipped.push(key);
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
frontmatter[key] = '';
|
|
84
|
-
}
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
const value = stripComment(raw);
|
|
88
|
-
const flow = value.match(/^\[(.*)\]$/);
|
|
89
|
-
if (flow) {
|
|
90
|
-
lists[key] = flow[1]
|
|
91
|
-
.split(',')
|
|
92
|
-
.map((v) => unquote(v))
|
|
93
|
-
.filter((v) => v !== '');
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
frontmatter[key] = unquote(value);
|
|
97
|
-
}
|
|
98
|
-
return { frontmatter, lists, skipped, body, present: true };
|
|
99
|
-
}
|
|
100
|
-
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/checks/frontmatter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* "html-rendering" — does the HTML delivered to a non-JS agent actually contain content?
|
|
4
|
-
*
|
|
5
|
-
* Most AI crawlers (GPTBot, ClaudeBot, CCBot, etc.) do **not** execute JavaScript. A site
|
|
6
|
-
* built as a client-rendered SPA returns an empty `<div id="root"></div>` shell to those
|
|
7
|
-
* agents, and is effectively invisible regardless of how good its `llms.txt` and structured
|
|
8
|
-
* data are. This check estimates server-side rendering by inspecting the static HTML body.
|
|
9
|
-
*
|
|
10
|
-
* Signals (each contributes to the score):
|
|
11
|
-
* - Visible text length and word count (low → likely JS-rendered shell)
|
|
12
|
-
* - Text-to-markup ratio against structural markup (high JS, no text → SPA)
|
|
13
|
-
* - Presence of semantic landmarks (`<main>`, `<article>`, `<header>`, `<footer>`, `<nav>`)
|
|
14
|
-
* - Single, meaningful `<h1>`
|
|
15
|
-
* - `<noscript>` fallback for JS-only frameworks
|
|
16
|
-
* - Non-empty SPA root containers (`#root`, `#app`, `#__next`)
|
|
17
|
-
* - Image `alt` attribute coverage
|
|
18
|
-
*/
|
|
19
|
-
export declare const meta: CheckMeta;
|
|
20
|
-
export default function check(ctx: CheckContext): Promise<CheckResult>;
|
|
21
|
-
//# sourceMappingURL=html-rendering.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"html-rendering.d.ts","sourceRoot":"","sources":["../../src/checks/html-rendering.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAUjF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,IAAI,EAAE,SAIlB,CAAC;AAOF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA6L3E"}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { guideUrl } from '../guide-urls.js';
|
|
2
|
-
import { countExecutableScripts, extractVisibleText, findOpeningTag, getMetaContent, structuralMarkupLength, } from './html-utils.js';
|
|
3
|
-
import { buildResult } from './utils.js';
|
|
4
|
-
/**
|
|
5
|
-
* "html-rendering" — does the HTML delivered to a non-JS agent actually contain content?
|
|
6
|
-
*
|
|
7
|
-
* Most AI crawlers (GPTBot, ClaudeBot, CCBot, etc.) do **not** execute JavaScript. A site
|
|
8
|
-
* built as a client-rendered SPA returns an empty `<div id="root"></div>` shell to those
|
|
9
|
-
* agents, and is effectively invisible regardless of how good its `llms.txt` and structured
|
|
10
|
-
* data are. This check estimates server-side rendering by inspecting the static HTML body.
|
|
11
|
-
*
|
|
12
|
-
* Signals (each contributes to the score):
|
|
13
|
-
* - Visible text length and word count (low → likely JS-rendered shell)
|
|
14
|
-
* - Text-to-markup ratio against structural markup (high JS, no text → SPA)
|
|
15
|
-
* - Presence of semantic landmarks (`<main>`, `<article>`, `<header>`, `<footer>`, `<nav>`)
|
|
16
|
-
* - Single, meaningful `<h1>`
|
|
17
|
-
* - `<noscript>` fallback for JS-only frameworks
|
|
18
|
-
* - Non-empty SPA root containers (`#root`, `#app`, `#__next`)
|
|
19
|
-
* - Image `alt` attribute coverage
|
|
20
|
-
*/
|
|
21
|
-
export const meta = {
|
|
22
|
-
id: 'html-rendering',
|
|
23
|
-
name: 'HTML Rendering',
|
|
24
|
-
description: 'Checks server-rendered content, semantic landmarks, and SPA-shell heuristics',
|
|
25
|
-
};
|
|
26
|
-
const MIN_TEXT_LENGTH = 500;
|
|
27
|
-
const MIN_WORD_COUNT = 80;
|
|
28
|
-
const MIN_TEXT_TO_HTML_RATIO = 0.05;
|
|
29
|
-
const SPA_ROOT_IDS = ['root', 'app', '__next', '__nuxt', 'svelte', 'gatsby', 'gatsby-focus-wrapper'];
|
|
30
|
-
export default async function check(ctx) {
|
|
31
|
-
const start = performance.now();
|
|
32
|
-
const findings = [];
|
|
33
|
-
let score = 100;
|
|
34
|
-
const html = ctx.html;
|
|
35
|
-
if (!html) {
|
|
36
|
-
findings.push({
|
|
37
|
-
status: 'fail',
|
|
38
|
-
message: 'Could not fetch homepage HTML',
|
|
39
|
-
hint: 'The homepage did not return any HTML body. Verify your server returns 200 OK with a non-empty response on the root URL.',
|
|
40
|
-
learnMoreUrl: guideUrl(meta.id, 'no-html'),
|
|
41
|
-
});
|
|
42
|
-
return buildResult(meta, 0, findings, start);
|
|
43
|
-
}
|
|
44
|
-
const visibleText = extractVisibleText(html);
|
|
45
|
-
const wordCount = visibleText ? visibleText.split(/\s+/).filter(Boolean).length : 0;
|
|
46
|
-
const textOk = visibleText.length >= MIN_TEXT_LENGTH && wordCount >= MIN_WORD_COUNT;
|
|
47
|
-
// Against the markup that could have carried text — script and style
|
|
48
|
-
// bodies, svg interiors and the head excluded (see structuralMarkupLength).
|
|
49
|
-
const markup = structuralMarkupLength(html);
|
|
50
|
-
const ratio = markup > 0 ? visibleText.length / markup : 0;
|
|
51
|
-
if (textOk) {
|
|
52
|
-
findings.push({
|
|
53
|
-
status: 'pass',
|
|
54
|
-
message: `Server-rendered content detected (${wordCount} words, ${visibleText.length} chars of visible text)`,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
else if (visibleText.length > 0) {
|
|
58
|
-
findings.push({
|
|
59
|
-
status: 'warn',
|
|
60
|
-
message: `Sparse server-rendered content (${wordCount} words, ${visibleText.length} chars)`,
|
|
61
|
-
detail: `Below thresholds: ${MIN_WORD_COUNT} words, ${MIN_TEXT_LENGTH} chars`,
|
|
62
|
-
hint: 'Render at least the main page content server-side. Many AI crawlers (GPTBot, ClaudeBot, CCBot) do not execute JavaScript and will see only the static HTML.',
|
|
63
|
-
learnMoreUrl: guideUrl(meta.id, 'sparse-content'),
|
|
64
|
-
});
|
|
65
|
-
score -= 25;
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
findings.push({
|
|
69
|
-
status: 'fail',
|
|
70
|
-
message: 'No visible text content in static HTML',
|
|
71
|
-
hint: 'Your homepage appears to be a JavaScript-only shell. Add server-side rendering (Next.js SSR/SSG, Astro, Remix, or a static prerender) so AI agents that do not execute JS can read your content.',
|
|
72
|
-
learnMoreUrl: guideUrl(meta.id, 'js-shell'),
|
|
73
|
-
});
|
|
74
|
-
score -= 50;
|
|
75
|
-
}
|
|
76
|
-
if (ratio >= MIN_TEXT_TO_HTML_RATIO) {
|
|
77
|
-
findings.push({
|
|
78
|
-
status: 'pass',
|
|
79
|
-
message: `Text-to-markup ratio is healthy (${(ratio * 100).toFixed(1)}% of structural markup)`,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
else if (textOk) {
|
|
83
|
-
// The ratio exists as a shell symptom, and the text thresholds just
|
|
84
|
-
// proved this page is not a shell: the words are demonstrably there.
|
|
85
|
-
// What a low ratio still says is that the structure around them is
|
|
86
|
-
// expensive for an agent consuming raw HTML — worth saying, not
|
|
87
|
-
// worth charging for twice.
|
|
88
|
-
findings.push({
|
|
89
|
-
status: 'warn',
|
|
90
|
-
message: `Markup-heavy page (text is ${(ratio * 100).toFixed(1)}% of structural markup)`,
|
|
91
|
-
detail: `Recommended minimum: ${(MIN_TEXT_TO_HTML_RATIO * 100).toFixed(0)}%. The content thresholds are met, so this does not move the score.`,
|
|
92
|
-
hint: 'The visible text clears the content thresholds, so agents can read this page. Trimming wrapper markup would still make it cheaper to consume.',
|
|
93
|
-
learnMoreUrl: guideUrl(meta.id, 'low-ratio'),
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
findings.push({
|
|
98
|
-
status: 'warn',
|
|
99
|
-
message: `Low text-to-markup ratio (${(ratio * 100).toFixed(1)}%)`,
|
|
100
|
-
detail: `Recommended minimum: ${(MIN_TEXT_TO_HTML_RATIO * 100).toFixed(0)}% of structural markup (script/style bodies and svg interiors excluded)`,
|
|
101
|
-
hint: 'A very low text-to-markup ratio is a typical SPA-shell symptom. Inline more content directly into the HTML response.',
|
|
102
|
-
learnMoreUrl: guideUrl(meta.id, 'low-ratio'),
|
|
103
|
-
});
|
|
104
|
-
score -= 10;
|
|
105
|
-
}
|
|
106
|
-
const emptyShell = SPA_ROOT_IDS.find((id) => {
|
|
107
|
-
const re = new RegExp(`<[^>]+\\bid\\s*=\\s*["']${id}["'][^>]*>\\s*</[^>]+>`, 'i');
|
|
108
|
-
return re.test(html);
|
|
109
|
-
});
|
|
110
|
-
if (emptyShell) {
|
|
111
|
-
findings.push({
|
|
112
|
-
status: 'fail',
|
|
113
|
-
message: `Empty SPA mount point detected: #${emptyShell}`,
|
|
114
|
-
hint: 'The static HTML contains an empty SPA mount node, which means content is rendered only client-side. Enable SSR/SSG so the initial HTML response includes meaningful content.',
|
|
115
|
-
learnMoreUrl: guideUrl(meta.id, 'empty-spa'),
|
|
116
|
-
});
|
|
117
|
-
score -= 20;
|
|
118
|
-
}
|
|
119
|
-
const semanticLandmarks = ['main', 'article', 'section', 'header', 'footer', 'nav'];
|
|
120
|
-
const presentLandmarks = semanticLandmarks.filter((tag) => findOpeningTag(html, tag) !== null);
|
|
121
|
-
if (presentLandmarks.length >= 3) {
|
|
122
|
-
findings.push({
|
|
123
|
-
status: 'pass',
|
|
124
|
-
message: `Semantic landmarks present (${presentLandmarks.join(', ')})`,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
else if (presentLandmarks.length > 0) {
|
|
128
|
-
findings.push({
|
|
129
|
-
status: 'warn',
|
|
130
|
-
message: `Only ${presentLandmarks.length} semantic landmark(s) found`,
|
|
131
|
-
detail: `Found: ${presentLandmarks.join(', ')}. Missing: ${semanticLandmarks.filter((t) => !presentLandmarks.includes(t)).join(', ')}`,
|
|
132
|
-
hint: 'Use semantic HTML tags so AI agents can understand page structure: <header>, <nav>, <main>, <article>, <section>, <footer>.',
|
|
133
|
-
learnMoreUrl: guideUrl(meta.id, 'few-landmarks'),
|
|
134
|
-
});
|
|
135
|
-
score -= 10;
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
findings.push({
|
|
139
|
-
status: 'warn',
|
|
140
|
-
message: 'No semantic HTML landmarks found',
|
|
141
|
-
hint: 'Replace generic <div> structures with semantic tags: <main>, <article>, <header>, <nav>, <footer>. Agents use these to identify the primary content region.',
|
|
142
|
-
learnMoreUrl: guideUrl(meta.id, 'no-landmarks'),
|
|
143
|
-
});
|
|
144
|
-
score -= 15;
|
|
145
|
-
}
|
|
146
|
-
const h1Matches = [...html.matchAll(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi)];
|
|
147
|
-
if (h1Matches.length === 1) {
|
|
148
|
-
const h1Text = extractVisibleText(h1Matches[0][1]);
|
|
149
|
-
if (h1Text.length > 0) {
|
|
150
|
-
findings.push({ status: 'pass', message: `Single <h1> heading: "${truncate(h1Text, 60)}"` });
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
findings.push({
|
|
154
|
-
status: 'warn',
|
|
155
|
-
message: '<h1> is empty',
|
|
156
|
-
hint: 'Add meaningful text inside your <h1> element so agents can identify the page topic.',
|
|
157
|
-
learnMoreUrl: guideUrl(meta.id, 'empty-h1'),
|
|
158
|
-
});
|
|
159
|
-
score -= 5;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
else if (h1Matches.length > 1) {
|
|
163
|
-
findings.push({
|
|
164
|
-
status: 'warn',
|
|
165
|
-
message: `${h1Matches.length} <h1> headings found (recommend exactly 1)`,
|
|
166
|
-
hint: 'Use a single <h1> per page to clearly mark the primary topic. Demote secondary headings to <h2>+.',
|
|
167
|
-
learnMoreUrl: guideUrl(meta.id, 'multiple-h1'),
|
|
168
|
-
});
|
|
169
|
-
score -= 5;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
findings.push({
|
|
173
|
-
status: 'warn',
|
|
174
|
-
message: 'No <h1> heading found',
|
|
175
|
-
hint: 'Add a single <h1> describing the page. Agents and search engines treat the H1 as the primary topic indicator.',
|
|
176
|
-
learnMoreUrl: guideUrl(meta.id, 'no-h1'),
|
|
177
|
-
});
|
|
178
|
-
score -= 10;
|
|
179
|
-
}
|
|
180
|
-
const heavyJs = countExecutableScripts(html) > 15;
|
|
181
|
-
const hasNoscript = /<noscript\b[^>]*>[\s\S]*?<\/noscript>/i.test(html);
|
|
182
|
-
if (heavyJs && !hasNoscript) {
|
|
183
|
-
findings.push({
|
|
184
|
-
status: 'warn',
|
|
185
|
-
message: 'Heavy JavaScript with no <noscript> fallback',
|
|
186
|
-
detail: `${countExecutableScripts(html)} executable <script> tags`,
|
|
187
|
-
hint: 'Provide a <noscript> fallback that explains the site or links to a non-JS variant. This helps agents that disable JS.',
|
|
188
|
-
learnMoreUrl: guideUrl(meta.id, 'no-noscript'),
|
|
189
|
-
});
|
|
190
|
-
score -= 5;
|
|
191
|
-
}
|
|
192
|
-
const imgs = [...html.matchAll(/<img\b([^>]*)>/gi)];
|
|
193
|
-
if (imgs.length > 0) {
|
|
194
|
-
const withAlt = imgs.filter((m) => /\balt\s*=/i.test(m[1])).length;
|
|
195
|
-
const coverage = withAlt / imgs.length;
|
|
196
|
-
if (coverage >= 0.9) {
|
|
197
|
-
findings.push({
|
|
198
|
-
status: 'pass',
|
|
199
|
-
message: `${withAlt}/${imgs.length} <img> tags have alt attributes`,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
findings.push({
|
|
204
|
-
status: 'warn',
|
|
205
|
-
message: `Only ${withAlt}/${imgs.length} <img> tags have alt attributes`,
|
|
206
|
-
hint: 'Add descriptive alt="" to every <img>. Agents use alt text to understand images they cannot process visually.',
|
|
207
|
-
learnMoreUrl: guideUrl(meta.id, 'missing-alt'),
|
|
208
|
-
});
|
|
209
|
-
score -= 5;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const generator = getMetaContent(html, 'generator');
|
|
213
|
-
if (generator) {
|
|
214
|
-
findings.push({ status: 'pass', message: `Generator: ${generator}` });
|
|
215
|
-
}
|
|
216
|
-
return buildResult(meta, score, findings, start);
|
|
217
|
-
}
|
|
218
|
-
function truncate(value, max) {
|
|
219
|
-
return value.length > max ? `${value.slice(0, max - 1)}…` : value;
|
|
220
|
-
}
|
|
221
|
-
//# sourceMappingURL=html-rendering.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"html-rendering.js","sourceRoot":"","sources":["../../src/checks/html-rendering.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared HTML parsing primitives used by checks that need to inspect homepage markup.
|
|
3
|
-
*
|
|
4
|
-
* These are deliberately regex-based (no DOM dependency) so the audit stays free of
|
|
5
|
-
* heavyweight HTML parsers. They are designed for resilience over completeness — every
|
|
6
|
-
* helper returns `null` / empty values on malformed input rather than throwing.
|
|
7
|
-
*/
|
|
8
|
-
/** Escape a string for safe inclusion inside a `RegExp` source. */
|
|
9
|
-
export declare function escapeRegex(value: string): string;
|
|
10
|
-
/** Decode the small set of HTML entities that show up most often inside attribute values. */
|
|
11
|
-
export declare function unescapeHtml(value: string): string;
|
|
12
|
-
/**
|
|
13
|
-
* Match the first opening tag (`<tag ...>`) and return its raw attribute string.
|
|
14
|
-
* Returns `null` if the tag is absent.
|
|
15
|
-
*/
|
|
16
|
-
export declare function findOpeningTag(html: string, tagName: string): string | null;
|
|
17
|
-
/** Pull a single attribute out of a raw attribute fragment (the `...` in `<tag ...>`). */
|
|
18
|
-
export declare function getAttribute(attrFragment: string, attrName: string): string | null;
|
|
19
|
-
/** Convenience: read an attribute from the first matching opening tag. */
|
|
20
|
-
export declare function getTagAttribute(html: string, tagName: string, attrName: string): string | null;
|
|
21
|
-
/**
|
|
22
|
-
* Extract the `content` value of the first `<meta name="..."|property="..."|http-equiv="...">`
|
|
23
|
-
* matching `key`. Searches `name`, `property`, and `http-equiv` (case-insensitive).
|
|
24
|
-
*/
|
|
25
|
-
export declare function getMetaContent(html: string, key: string): string | null;
|
|
26
|
-
/** Match every `<meta>` tag whose name/property begins with `prefix` (e.g. `og:`, `twitter:`). */
|
|
27
|
-
export declare function findMetaTagsByPrefix(html: string, prefix: string): string[];
|
|
28
|
-
/** Match every `<link rel="...">` tag with the requested `rel` value. */
|
|
29
|
-
export declare function findLinkTags(html: string, rel: string): string[];
|
|
30
|
-
/**
|
|
31
|
-
* Strip `<script>` / `<style>` blocks and HTML tags to produce a rough text-content estimate.
|
|
32
|
-
* Returns the trimmed plain text — used for content-density / SSR heuristics.
|
|
33
|
-
*/
|
|
34
|
-
export declare function extractVisibleText(html: string): string;
|
|
35
|
-
/**
|
|
36
|
-
* Extract the contents of every `<script type="application/ld+json">` block.
|
|
37
|
-
*
|
|
38
|
-
* Attribute order matters here, and it is easy to get wrong: a pattern anchored
|
|
39
|
-
* on `<script type=...` misses `<script id="org-json-ld" type="application/ld+json">`,
|
|
40
|
-
* which is how several frameworks emit it. That mistake reads as "no structured
|
|
41
|
-
* data found" on a site whose markup is perfectly good, so the matcher tolerates
|
|
42
|
-
* any attributes before `type`.
|
|
43
|
-
*/
|
|
44
|
-
export declare function findJsonLdBlocks(html: string): string[];
|
|
45
|
-
/** Count `<script>` tags (excluding `type="application/ld+json"` data blocks). */
|
|
46
|
-
export declare function countExecutableScripts(html: string): number;
|
|
47
|
-
/**
|
|
48
|
-
* How much of the document is markup that could have carried visible text.
|
|
49
|
-
*
|
|
50
|
-
* The denominator for the text-to-markup ratio. Three element classes are
|
|
51
|
-
* emptied rather than counted, because their contents are not markup an
|
|
52
|
-
* agent parses for content: script bodies are code or serialized data (a
|
|
53
|
-
* framework's hydration payload alone can be ten times the page), style
|
|
54
|
-
* bodies are presentation, and svg interiors are drawing instructions —
|
|
55
|
-
* the inline twin of image bytes, which nobody would count against an
|
|
56
|
-
* `<img>`. Comments likewise. The head is metadata by definition — much
|
|
57
|
-
* of it demanded by other checks in this engine — so the measurement
|
|
58
|
-
* starts at `<body>` when one exists.
|
|
59
|
-
*
|
|
60
|
-
* Measured against the raw document instead, vercel.com, stripe.com and
|
|
61
|
-
* nextjs.org all sat under the 5% threshold while serving hundreds of
|
|
62
|
-
* words of visible text: the raw ratio graded framework choice, not
|
|
63
|
-
* agent experience.
|
|
64
|
-
*/
|
|
65
|
-
export declare function structuralMarkupLength(html: string): number;
|
|
66
|
-
//# sourceMappingURL=html-utils.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"html-utils.d.ts","sourceRoot":"","sources":["../../src/checks/html-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3E;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKlF;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMvE;AAED,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAG3E;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAGhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASvD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQvD;AAED,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS3D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ3D"}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared HTML parsing primitives used by checks that need to inspect homepage markup.
|
|
3
|
-
*
|
|
4
|
-
* These are deliberately regex-based (no DOM dependency) so the audit stays free of
|
|
5
|
-
* heavyweight HTML parsers. They are designed for resilience over completeness — every
|
|
6
|
-
* helper returns `null` / empty values on malformed input rather than throwing.
|
|
7
|
-
*/
|
|
8
|
-
/** Escape a string for safe inclusion inside a `RegExp` source. */
|
|
9
|
-
export function escapeRegex(value) {
|
|
10
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
11
|
-
}
|
|
12
|
-
/** Decode the small set of HTML entities that show up most often inside attribute values. */
|
|
13
|
-
export function unescapeHtml(value) {
|
|
14
|
-
return value
|
|
15
|
-
.replace(/&/g, '&')
|
|
16
|
-
.replace(/</g, '<')
|
|
17
|
-
.replace(/>/g, '>')
|
|
18
|
-
.replace(/"/g, '"')
|
|
19
|
-
.replace(/'/g, "'")
|
|
20
|
-
.replace(/'/g, "'")
|
|
21
|
-
.replace(///g, '/');
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Match the first opening tag (`<tag ...>`) and return its raw attribute string.
|
|
25
|
-
* Returns `null` if the tag is absent.
|
|
26
|
-
*/
|
|
27
|
-
export function findOpeningTag(html, tagName) {
|
|
28
|
-
const re = new RegExp(`<${escapeRegex(tagName)}\\b([^>]*)>`, 'i');
|
|
29
|
-
const m = html.match(re);
|
|
30
|
-
return m ? m[1] : null;
|
|
31
|
-
}
|
|
32
|
-
/** Pull a single attribute out of a raw attribute fragment (the `...` in `<tag ...>`). */
|
|
33
|
-
export function getAttribute(attrFragment, attrName) {
|
|
34
|
-
const re = new RegExp(`\\b${escapeRegex(attrName)}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, 'i');
|
|
35
|
-
const m = attrFragment.match(re);
|
|
36
|
-
if (!m)
|
|
37
|
-
return null;
|
|
38
|
-
return unescapeHtml(m[1] ?? m[2] ?? m[3] ?? '');
|
|
39
|
-
}
|
|
40
|
-
/** Convenience: read an attribute from the first matching opening tag. */
|
|
41
|
-
export function getTagAttribute(html, tagName, attrName) {
|
|
42
|
-
const frag = findOpeningTag(html, tagName);
|
|
43
|
-
return frag === null ? null : getAttribute(frag, attrName);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Extract the `content` value of the first `<meta name="..."|property="..."|http-equiv="...">`
|
|
47
|
-
* matching `key`. Searches `name`, `property`, and `http-equiv` (case-insensitive).
|
|
48
|
-
*/
|
|
49
|
-
export function getMetaContent(html, key) {
|
|
50
|
-
const escaped = escapeRegex(key);
|
|
51
|
-
const re = new RegExp(`<meta\\s+[^>]*\\b(?:name|property|http-equiv)\\s*=\\s*["']${escaped}["'][^>]*>`, 'i');
|
|
52
|
-
const m = html.match(re);
|
|
53
|
-
if (!m)
|
|
54
|
-
return null;
|
|
55
|
-
return getAttribute(m[0], 'content');
|
|
56
|
-
}
|
|
57
|
-
/** Match every `<meta>` tag whose name/property begins with `prefix` (e.g. `og:`, `twitter:`). */
|
|
58
|
-
export function findMetaTagsByPrefix(html, prefix) {
|
|
59
|
-
const re = new RegExp(`<meta\\s+[^>]*\\b(?:name|property)\\s*=\\s*["']${escapeRegex(prefix)}[^"']*["'][^>]*>`, 'gi');
|
|
60
|
-
return [...html.matchAll(re)].map((m) => m[0]);
|
|
61
|
-
}
|
|
62
|
-
/** Match every `<link rel="...">` tag with the requested `rel` value. */
|
|
63
|
-
export function findLinkTags(html, rel) {
|
|
64
|
-
const re = new RegExp(`<link\\s+[^>]*\\brel\\s*=\\s*["'][^"']*\\b${escapeRegex(rel)}\\b[^"']*["'][^>]*>`, 'gi');
|
|
65
|
-
return [...html.matchAll(re)].map((m) => m[0]);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Strip `<script>` / `<style>` blocks and HTML tags to produce a rough text-content estimate.
|
|
69
|
-
* Returns the trimmed plain text — used for content-density / SSR heuristics.
|
|
70
|
-
*/
|
|
71
|
-
export function extractVisibleText(html) {
|
|
72
|
-
return html
|
|
73
|
-
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
|
|
74
|
-
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
|
|
75
|
-
.replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, ' ')
|
|
76
|
-
.replace(/<!--[\s\S]*?-->/g, ' ')
|
|
77
|
-
.replace(/<[^>]+>/g, ' ')
|
|
78
|
-
.replace(/\s+/g, ' ')
|
|
79
|
-
.trim();
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Extract the contents of every `<script type="application/ld+json">` block.
|
|
83
|
-
*
|
|
84
|
-
* Attribute order matters here, and it is easy to get wrong: a pattern anchored
|
|
85
|
-
* on `<script type=...` misses `<script id="org-json-ld" type="application/ld+json">`,
|
|
86
|
-
* which is how several frameworks emit it. That mistake reads as "no structured
|
|
87
|
-
* data found" on a site whose markup is perfectly good, so the matcher tolerates
|
|
88
|
-
* any attributes before `type`.
|
|
89
|
-
*/
|
|
90
|
-
export function findJsonLdBlocks(html) {
|
|
91
|
-
const re = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
92
|
-
const blocks = [];
|
|
93
|
-
for (const m of html.matchAll(re)) {
|
|
94
|
-
const type = (getAttribute(m[1], 'type') ?? '').toLowerCase();
|
|
95
|
-
if (type.includes('application/ld+json'))
|
|
96
|
-
blocks.push(m[2]);
|
|
97
|
-
}
|
|
98
|
-
return blocks;
|
|
99
|
-
}
|
|
100
|
-
/** Count `<script>` tags (excluding `type="application/ld+json"` data blocks). */
|
|
101
|
-
export function countExecutableScripts(html) {
|
|
102
|
-
const re = /<script\b([^>]*)>/gi;
|
|
103
|
-
let count = 0;
|
|
104
|
-
for (const m of html.matchAll(re)) {
|
|
105
|
-
const type = getAttribute(m[1], 'type') ?? '';
|
|
106
|
-
if (/json|template/i.test(type))
|
|
107
|
-
continue;
|
|
108
|
-
count++;
|
|
109
|
-
}
|
|
110
|
-
return count;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* How much of the document is markup that could have carried visible text.
|
|
114
|
-
*
|
|
115
|
-
* The denominator for the text-to-markup ratio. Three element classes are
|
|
116
|
-
* emptied rather than counted, because their contents are not markup an
|
|
117
|
-
* agent parses for content: script bodies are code or serialized data (a
|
|
118
|
-
* framework's hydration payload alone can be ten times the page), style
|
|
119
|
-
* bodies are presentation, and svg interiors are drawing instructions —
|
|
120
|
-
* the inline twin of image bytes, which nobody would count against an
|
|
121
|
-
* `<img>`. Comments likewise. The head is metadata by definition — much
|
|
122
|
-
* of it demanded by other checks in this engine — so the measurement
|
|
123
|
-
* starts at `<body>` when one exists.
|
|
124
|
-
*
|
|
125
|
-
* Measured against the raw document instead, vercel.com, stripe.com and
|
|
126
|
-
* nextjs.org all sat under the 5% threshold while serving hundreds of
|
|
127
|
-
* words of visible text: the raw ratio graded framework choice, not
|
|
128
|
-
* agent experience.
|
|
129
|
-
*/
|
|
130
|
-
export function structuralMarkupLength(html) {
|
|
131
|
-
const stripped = html
|
|
132
|
-
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '<script></script>')
|
|
133
|
-
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '<style></style>')
|
|
134
|
-
.replace(/<svg\b[\s\S]*?<\/svg>/gi, '<svg></svg>')
|
|
135
|
-
.replace(/<!--[\s\S]*?-->/g, '');
|
|
136
|
-
const body = stripped.match(/<body[\s\S]*<\/body>/i);
|
|
137
|
-
return (body ? body[0] : stripped).length;
|
|
138
|
-
}
|
|
139
|
-
//# sourceMappingURL=html-utils.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"html-utils.js","sourceRoot":"","sources":["../../src/checks/html-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { CheckContext, CheckResult, CheckMeta } from '../types.js';
|
|
2
|
-
interface LinkEntry {
|
|
3
|
-
url: string;
|
|
4
|
-
params: Record<string, string>;
|
|
5
|
-
}
|
|
6
|
-
/** Parse an RFC 5988 Link header into structured entries. */
|
|
7
|
-
export declare function parseLinkHeader(header: string): LinkEntry[];
|
|
8
|
-
export declare const meta: CheckMeta;
|
|
9
|
-
export default function check(ctx: CheckContext): Promise<CheckResult>;
|
|
10
|
-
export {};
|
|
11
|
-
//# sourceMappingURL=http-headers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http-headers.d.ts","sourceRoot":"","sources":["../../src/checks/http-headers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAW,MAAM,aAAa,CAAC;AAGjF,UAAU,SAAS;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CA2B3D;AAsFD,eAAO,MAAM,IAAI,EAAE,SAKlB,CAAC;AAwBF,wBAA8B,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAoH3E"}
|