afdocs 0.1.0 → 0.2.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.
Files changed (66) hide show
  1. package/README.md +42 -21
  2. package/dist/checks/authentication/auth-alternative-access.d.ts +2 -0
  3. package/dist/checks/authentication/auth-alternative-access.d.ts.map +1 -0
  4. package/dist/checks/authentication/auth-alternative-access.js +17 -0
  5. package/dist/checks/authentication/auth-alternative-access.js.map +1 -0
  6. package/dist/checks/authentication/auth-gate-detection.d.ts +2 -0
  7. package/dist/checks/authentication/auth-gate-detection.d.ts.map +1 -0
  8. package/dist/checks/authentication/auth-gate-detection.js +17 -0
  9. package/dist/checks/authentication/auth-gate-detection.js.map +1 -0
  10. package/dist/checks/index.d.ts +2 -0
  11. package/dist/checks/index.d.ts.map +1 -1
  12. package/dist/checks/index.js +3 -0
  13. package/dist/checks/index.js.map +1 -1
  14. package/dist/checks/llms-txt/llms-txt-exists.js +83 -6
  15. package/dist/checks/llms-txt/llms-txt-exists.js.map +1 -1
  16. package/dist/checks/llms-txt/llms-txt-links-markdown.js +46 -21
  17. package/dist/checks/llms-txt/llms-txt-links-markdown.js.map +1 -1
  18. package/dist/checks/llms-txt/llms-txt-links-resolve.js +10 -3
  19. package/dist/checks/llms-txt/llms-txt-links-resolve.js.map +1 -1
  20. package/dist/checks/markdown-availability/content-negotiation.js +101 -5
  21. package/dist/checks/markdown-availability/content-negotiation.js.map +1 -1
  22. package/dist/checks/markdown-availability/markdown-url-support.js +82 -5
  23. package/dist/checks/markdown-availability/markdown-url-support.js.map +1 -1
  24. package/dist/checks/page-size/content-start-position.js +161 -5
  25. package/dist/checks/page-size/content-start-position.js.map +1 -1
  26. package/dist/checks/page-size/page-size-html.js +127 -5
  27. package/dist/checks/page-size/page-size-html.js.map +1 -1
  28. package/dist/checks/page-size/page-size-markdown.js +183 -5
  29. package/dist/checks/page-size/page-size-markdown.js.map +1 -1
  30. package/dist/cli/commands/check.d.ts.map +1 -1
  31. package/dist/cli/commands/check.js +9 -1
  32. package/dist/cli/commands/check.js.map +1 -1
  33. package/dist/cli/formatters/text.d.ts +4 -1
  34. package/dist/cli/formatters/text.d.ts.map +1 -1
  35. package/dist/cli/formatters/text.js +94 -1
  36. package/dist/cli/formatters/text.js.map +1 -1
  37. package/dist/constants.d.ts +6 -0
  38. package/dist/constants.d.ts.map +1 -1
  39. package/dist/constants.js +3 -0
  40. package/dist/constants.js.map +1 -1
  41. package/dist/helpers/detect-markdown.d.ts +10 -0
  42. package/dist/helpers/detect-markdown.d.ts.map +1 -0
  43. package/dist/helpers/detect-markdown.js +29 -0
  44. package/dist/helpers/detect-markdown.js.map +1 -0
  45. package/dist/helpers/get-page-urls.d.ts +39 -0
  46. package/dist/helpers/get-page-urls.d.ts.map +1 -0
  47. package/dist/helpers/get-page-urls.js +230 -0
  48. package/dist/helpers/get-page-urls.js.map +1 -0
  49. package/dist/helpers/html-to-markdown.d.ts +7 -0
  50. package/dist/helpers/html-to-markdown.d.ts.map +1 -0
  51. package/dist/helpers/html-to-markdown.js +11 -0
  52. package/dist/helpers/html-to-markdown.js.map +1 -0
  53. package/dist/helpers/index.d.ts +5 -0
  54. package/dist/helpers/index.d.ts.map +1 -1
  55. package/dist/helpers/index.js +4 -0
  56. package/dist/helpers/index.js.map +1 -1
  57. package/dist/helpers/to-md-urls.d.ts +7 -0
  58. package/dist/helpers/to-md-urls.d.ts.map +1 -0
  59. package/dist/helpers/to-md-urls.js +24 -0
  60. package/dist/helpers/to-md-urls.js.map +1 -0
  61. package/dist/runner.d.ts.map +1 -1
  62. package/dist/runner.js +21 -14
  63. package/dist/runner.js.map +1 -1
  64. package/dist/types.d.ts +10 -1
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +8 -2
@@ -1,10 +1,106 @@
1
1
  import { registerCheck } from '../registry.js';
2
- async function check(_ctx) {
2
+ import { looksLikeMarkdown } from '../../helpers/detect-markdown.js';
3
+ import { discoverAndSamplePages } from '../../helpers/get-page-urls.js';
4
+ async function check(ctx) {
5
+ const id = 'content-negotiation';
6
+ const category = 'markdown-availability';
7
+ const { urls: pageUrls, totalPages, sampled: wasSampled, warnings, } = await discoverAndSamplePages(ctx);
8
+ const results = [];
9
+ const concurrency = ctx.options.maxConcurrency;
10
+ for (let i = 0; i < pageUrls.length; i += concurrency) {
11
+ const batch = pageUrls.slice(i, i + concurrency);
12
+ const batchResults = await Promise.all(batch.map(async (url) => {
13
+ try {
14
+ const response = await ctx.http.fetch(url, {
15
+ headers: { Accept: 'text/markdown' },
16
+ });
17
+ const body = await response.text();
18
+ const contentType = response.headers.get('content-type') ?? '';
19
+ const isMarkdownType = contentType.includes('text/markdown');
20
+ const isMarkdownBody = looksLikeMarkdown(body);
21
+ let classification;
22
+ if (isMarkdownType && isMarkdownBody) {
23
+ classification = 'markdown-with-correct-type';
24
+ // Cache the markdown content (only if not already cached by md-url check)
25
+ if (!ctx.pageCache.has(url)) {
26
+ ctx.pageCache.set(url, {
27
+ url,
28
+ markdown: { content: body, source: 'content-negotiation' },
29
+ });
30
+ }
31
+ }
32
+ else if (isMarkdownBody) {
33
+ classification = 'markdown-with-wrong-type';
34
+ if (!ctx.pageCache.has(url)) {
35
+ ctx.pageCache.set(url, {
36
+ url,
37
+ markdown: { content: body, source: 'content-negotiation' },
38
+ });
39
+ }
40
+ }
41
+ else {
42
+ classification = 'html';
43
+ }
44
+ return { url, classification, contentType, status: response.status };
45
+ }
46
+ catch (err) {
47
+ return {
48
+ url,
49
+ classification: 'html',
50
+ contentType: '',
51
+ status: 0,
52
+ error: err instanceof Error ? err.message : String(err),
53
+ };
54
+ }
55
+ }));
56
+ results.push(...batchResults);
57
+ }
58
+ const markdownWithCorrectType = results.filter((r) => r.classification === 'markdown-with-correct-type').length;
59
+ const markdownWithWrongType = results.filter((r) => r.classification === 'markdown-with-wrong-type').length;
60
+ const htmlOnly = results.filter((r) => r.classification === 'html').length;
61
+ const negotiationRate = Math.round((markdownWithCorrectType / results.length) * 100);
62
+ const fetchErrors = results.filter((r) => r.error).length;
63
+ const rateLimited = results.filter((r) => r.status === 429).length;
64
+ const pageLabel = wasSampled ? 'sampled pages' : 'pages';
65
+ const suffix = (fetchErrors > 0 ? `; ${fetchErrors} failed to fetch` : '') +
66
+ (rateLimited > 0 ? `; ${rateLimited} rate-limited (HTTP 429)` : '');
67
+ const details = {
68
+ totalPages,
69
+ testedPages: results.length,
70
+ sampled: wasSampled,
71
+ markdownWithCorrectType,
72
+ markdownWithWrongType,
73
+ htmlOnly,
74
+ negotiationRate,
75
+ fetchErrors,
76
+ rateLimited,
77
+ pageResults: results,
78
+ discoveryWarnings: warnings,
79
+ };
80
+ if (negotiationRate >= 90) {
81
+ return {
82
+ id,
83
+ category,
84
+ status: 'pass',
85
+ message: `${markdownWithCorrectType}/${results.length} ${pageLabel} support content negotiation (${negotiationRate}%)${suffix}`,
86
+ details,
87
+ };
88
+ }
89
+ if (markdownWithCorrectType > 0 || markdownWithWrongType > 0) {
90
+ return {
91
+ id,
92
+ category,
93
+ status: 'warn',
94
+ message: `Content negotiation partially supported: ${markdownWithCorrectType} correct type, ${markdownWithWrongType} wrong type, ${htmlOnly} HTML only${suffix}`,
95
+ details,
96
+ };
97
+ }
3
98
  return {
4
- id: 'content-negotiation',
5
- category: 'markdown-availability',
6
- status: 'skip',
7
- message: 'Not yet implemented',
99
+ id,
100
+ category,
101
+ status: 'fail',
102
+ message: `Server ignores Accept: text/markdown header (0/${results.length} ${pageLabel} return markdown)${suffix}`,
103
+ details,
8
104
  };
9
105
  }
10
106
  registerCheck({
@@ -1 +1 @@
1
- {"version":3,"file":"content-negotiation.js","sourceRoot":"","sources":["../../../src/checks/markdown-availability/content-negotiation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C,KAAK,UAAU,KAAK,CAAC,IAAkB;IACrC,OAAO;QACL,EAAE,EAAE,qBAAqB;QACzB,QAAQ,EAAE,uBAAuB;QACjC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,qBAAqB;KAC/B,CAAC;AACJ,CAAC;AAED,aAAa,CAAC;IACZ,EAAE,EAAE,qBAAqB;IACzB,QAAQ,EAAE,uBAAuB;IACjC,WAAW,EAAE,sDAAsD;IACnE,SAAS,EAAE,EAAE;IACb,GAAG,EAAE,KAAK;CACX,CAAC,CAAC"}
1
+ {"version":3,"file":"content-negotiation.js","sourceRoot":"","sources":["../../../src/checks/markdown-availability/content-negotiation.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,87 @@
1
1
  import { registerCheck } from '../registry.js';
2
- async function check(_ctx) {
2
+ import { looksLikeMarkdown } from '../../helpers/detect-markdown.js';
3
+ import { discoverAndSamplePages } from '../../helpers/get-page-urls.js';
4
+ import { toMdUrls } from '../../helpers/to-md-urls.js';
5
+ async function check(ctx) {
6
+ const id = 'markdown-url-support';
7
+ const category = 'markdown-availability';
8
+ const { urls: pageUrls, totalPages, sampled: wasSampled, warnings, } = await discoverAndSamplePages(ctx);
9
+ const results = [];
10
+ const concurrency = ctx.options.maxConcurrency;
11
+ for (let i = 0; i < pageUrls.length; i += concurrency) {
12
+ const batch = pageUrls.slice(i, i + concurrency);
13
+ const batchResults = await Promise.all(batch.map(async (url) => {
14
+ const candidates = toMdUrls(url);
15
+ let lastError;
16
+ for (const mdUrl of candidates) {
17
+ try {
18
+ const response = await ctx.http.fetch(mdUrl);
19
+ const body = await response.text();
20
+ const contentType = response.headers.get('content-type') ?? '';
21
+ const isMarkdownType = contentType.includes('text/markdown');
22
+ const isMarkdownBody = looksLikeMarkdown(body);
23
+ const supported = response.ok && (isMarkdownType || isMarkdownBody);
24
+ if (supported) {
25
+ ctx.pageCache.set(url, {
26
+ url,
27
+ markdown: { content: body, source: 'md-url' },
28
+ });
29
+ return { url, mdUrl, supported: true, status: response.status };
30
+ }
31
+ lastError = undefined; // Got a response, not a fetch error
32
+ }
33
+ catch (err) {
34
+ lastError = err instanceof Error ? err.message : String(err);
35
+ }
36
+ }
37
+ return { url, mdUrl: candidates[0], supported: false, status: 0, error: lastError };
38
+ }));
39
+ results.push(...batchResults);
40
+ }
41
+ const mdSupported = results.filter((r) => r.supported).length;
42
+ const mdUnsupported = results.length - mdSupported;
43
+ const supportRate = Math.round((mdSupported / results.length) * 100);
44
+ const fetchErrors = results.filter((r) => r.error).length;
45
+ const rateLimited = results.filter((r) => r.status === 429).length;
46
+ const pageLabel = wasSampled ? 'sampled pages' : 'pages';
47
+ const suffix = (fetchErrors > 0 ? `; ${fetchErrors} failed to fetch` : '') +
48
+ (rateLimited > 0 ? `; ${rateLimited} rate-limited (HTTP 429)` : '');
49
+ const details = {
50
+ totalPages,
51
+ testedPages: results.length,
52
+ sampled: wasSampled,
53
+ mdSupported,
54
+ mdUnsupported,
55
+ supportRate,
56
+ fetchErrors,
57
+ rateLimited,
58
+ pageResults: results,
59
+ discoveryWarnings: warnings,
60
+ };
61
+ if (supportRate >= 90) {
62
+ return {
63
+ id,
64
+ category,
65
+ status: 'pass',
66
+ message: `${mdSupported}/${results.length} ${pageLabel} support .md URLs (${supportRate}%)${suffix}`,
67
+ details,
68
+ };
69
+ }
70
+ if (mdSupported > 0) {
71
+ return {
72
+ id,
73
+ category,
74
+ status: 'warn',
75
+ message: `${mdSupported}/${results.length} ${pageLabel} support .md URLs (${supportRate}%); inconsistent support${suffix}`,
76
+ details,
77
+ };
78
+ }
3
79
  return {
4
- id: 'markdown-url-support',
5
- category: 'markdown-availability',
6
- status: 'skip',
7
- message: 'Not yet implemented',
80
+ id,
81
+ category,
82
+ status: 'fail',
83
+ message: `No ${pageLabel} support .md URLs (0/${results.length} tested)${suffix}`,
84
+ details,
8
85
  };
9
86
  }
10
87
  registerCheck({
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-url-support.js","sourceRoot":"","sources":["../../../src/checks/markdown-availability/markdown-url-support.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C,KAAK,UAAU,KAAK,CAAC,IAAkB;IACrC,OAAO;QACL,EAAE,EAAE,sBAAsB;QAC1B,QAAQ,EAAE,uBAAuB;QACjC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,qBAAqB;KAC/B,CAAC;AACJ,CAAC;AAED,aAAa,CAAC;IACZ,EAAE,EAAE,sBAAsB;IAC1B,QAAQ,EAAE,uBAAuB;IACjC,WAAW,EAAE,2DAA2D;IACxE,SAAS,EAAE,EAAE;IACb,GAAG,EAAE,KAAK;CACX,CAAC,CAAC"}
1
+ {"version":3,"file":"markdown-url-support.js","sourceRoot":"","sources":["../../../src/checks/markdown-availability/markdown-url-support.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,166 @@
1
1
  import { registerCheck } from '../registry.js';
2
- async function check(_ctx) {
2
+ import { looksLikeHtml } from '../../helpers/detect-markdown.js';
3
+ import { discoverAndSamplePages } from '../../helpers/get-page-urls.js';
4
+ import { htmlToMarkdown } from '../../helpers/html-to-markdown.js';
5
+ const CSS_PATTERN = /[{}\s]*[a-z-]+\s*:\s*[^;]+;/;
6
+ const JS_PATTERNS = [/^\s*(function|var|const|let|import|export)\b/, /^\s*\/\//, /[{};]\s*$/];
7
+ const NAV_MAX_LENGTH = 40;
8
+ /**
9
+ * Find the character position where meaningful content begins in converted markdown.
10
+ * Meaningful content is a heading or a prose paragraph (not CSS, JS, or short nav text).
11
+ */
12
+ function findContentStart(markdown) {
13
+ const lines = markdown.split('\n');
14
+ let charPos = 0;
15
+ for (let idx = 0; idx < lines.length; idx++) {
16
+ const line = lines[idx];
17
+ const trimmed = line.trim();
18
+ if (trimmed.length === 0) {
19
+ charPos += line.length + 1;
20
+ continue;
21
+ }
22
+ // ATX heading: starts with # at beginning of line
23
+ if (/^#{1,6}\s+\S/.test(trimmed)) {
24
+ return charPos;
25
+ }
26
+ // Setext heading: current line is text, next line is === or ---
27
+ const nextLine = idx + 1 < lines.length ? lines[idx + 1].trim() : '';
28
+ if (/^[=-]+$/.test(nextLine) && nextLine.length >= 2 && trimmed.length > 0) {
29
+ return charPos;
30
+ }
31
+ // Skip CSS-like lines
32
+ if (CSS_PATTERN.test(trimmed)) {
33
+ charPos += line.length + 1;
34
+ continue;
35
+ }
36
+ // Skip JS-like lines
37
+ if (JS_PATTERNS.some((p) => p.test(trimmed))) {
38
+ charPos += line.length + 1;
39
+ continue;
40
+ }
41
+ // Skip very short nav-like tokens (e.g., "Home", "Docs", "API")
42
+ if (trimmed.length <= NAV_MAX_LENGTH && !/[.!?]/.test(trimmed) && !trimmed.includes(' ')) {
43
+ charPos += line.length + 1;
44
+ continue;
45
+ }
46
+ // Prose-like paragraph: contains spaces (multiple words) and is reasonably long
47
+ if (trimmed.length > NAV_MAX_LENGTH || (trimmed.includes(' ') && trimmed.length > 20)) {
48
+ return charPos;
49
+ }
50
+ charPos += line.length + 1;
51
+ }
52
+ // If nothing matched, content starts at the end (all boilerplate)
53
+ return charPos;
54
+ }
55
+ function positionStatus(percent) {
56
+ if (percent <= 10)
57
+ return 'pass';
58
+ if (percent <= 50)
59
+ return 'warn';
60
+ return 'fail';
61
+ }
62
+ function worstStatus(statuses) {
63
+ if (statuses.includes('fail'))
64
+ return 'fail';
65
+ if (statuses.includes('warn'))
66
+ return 'warn';
67
+ return 'pass';
68
+ }
69
+ async function check(ctx) {
70
+ const id = 'content-start-position';
71
+ const category = 'page-size';
72
+ const { urls: pageUrls, totalPages, sampled: wasSampled, warnings, } = await discoverAndSamplePages(ctx);
73
+ const results = [];
74
+ const concurrency = ctx.options.maxConcurrency;
75
+ for (let i = 0; i < pageUrls.length; i += concurrency) {
76
+ const batch = pageUrls.slice(i, i + concurrency);
77
+ const batchResults = await Promise.all(batch.map(async (url) => {
78
+ try {
79
+ const response = await ctx.http.fetch(url);
80
+ const body = await response.text();
81
+ const contentType = response.headers.get('content-type') ?? '';
82
+ const isMarkdownType = contentType.includes('text/markdown') || contentType.includes('text/plain');
83
+ const isHtml = !isMarkdownType && (contentType.includes('text/html') || looksLikeHtml(body));
84
+ const markdown = isHtml ? htmlToMarkdown(body) : body;
85
+ const totalChars = markdown.length;
86
+ const contentStartChar = findContentStart(markdown);
87
+ const contentStartPercent = totalChars > 0 ? Math.round((contentStartChar / totalChars) * 100) : 0;
88
+ return {
89
+ url,
90
+ contentStartChar,
91
+ totalChars,
92
+ contentStartPercent,
93
+ status: positionStatus(contentStartPercent),
94
+ };
95
+ }
96
+ catch (err) {
97
+ return {
98
+ url,
99
+ contentStartChar: 0,
100
+ totalChars: 0,
101
+ contentStartPercent: 100,
102
+ status: 'fail',
103
+ error: err instanceof Error ? err.message : String(err),
104
+ };
105
+ }
106
+ }));
107
+ results.push(...batchResults);
108
+ }
109
+ const successful = results.filter((r) => !r.error);
110
+ const fetchErrors = results.filter((r) => r.error).length;
111
+ if (successful.length === 0) {
112
+ return {
113
+ id,
114
+ category,
115
+ status: 'fail',
116
+ message: `Could not fetch any pages to analyze${fetchErrors > 0 ? `; ${fetchErrors} failed to fetch` : ''}`,
117
+ details: {
118
+ totalPages,
119
+ testedPages: results.length,
120
+ sampled: wasSampled,
121
+ fetchErrors,
122
+ pageResults: results,
123
+ discoveryWarnings: warnings,
124
+ },
125
+ };
126
+ }
127
+ const percents = successful.map((r) => r.contentStartPercent);
128
+ const medianPercent = percents.sort((a, b) => a - b)[Math.floor(percents.length / 2)];
129
+ const maxPercent = Math.max(...percents);
130
+ const overallStatus = worstStatus(successful.map((r) => r.status));
131
+ const pageLabel = wasSampled ? 'sampled pages' : 'pages';
132
+ const passBucket = successful.filter((r) => r.status === 'pass').length;
133
+ const warnBucket = successful.filter((r) => r.status === 'warn').length;
134
+ const failBucket = successful.filter((r) => r.status === 'fail').length;
135
+ const suffix = fetchErrors > 0 ? `; ${fetchErrors} failed to fetch` : '';
136
+ let message;
137
+ if (overallStatus === 'pass') {
138
+ message = `Content starts within first 10% on all ${successful.length} ${pageLabel} (median ${medianPercent}%)${suffix}`;
139
+ }
140
+ else if (overallStatus === 'warn') {
141
+ message = `${warnBucket} of ${successful.length} ${pageLabel} have content starting at 10–50% (worst ${maxPercent}%)${suffix}`;
142
+ }
143
+ else {
144
+ message = `${failBucket} of ${successful.length} ${pageLabel} have content starting past 50% (worst ${maxPercent}%)${suffix}`;
145
+ }
3
146
  return {
4
- id: 'content-start-position',
5
- category: 'page-size',
6
- status: 'skip',
7
- message: 'Not yet implemented',
147
+ id,
148
+ category,
149
+ status: overallStatus,
150
+ message,
151
+ details: {
152
+ totalPages,
153
+ testedPages: results.length,
154
+ sampled: wasSampled,
155
+ medianPercent,
156
+ maxPercent,
157
+ passBucket,
158
+ warnBucket,
159
+ failBucket,
160
+ fetchErrors,
161
+ pageResults: results,
162
+ discoveryWarnings: warnings,
163
+ },
8
164
  };
9
165
  }
10
166
  registerCheck({
@@ -1 +1 @@
1
- {"version":3,"file":"content-start-position.js","sourceRoot":"","sources":["../../../src/checks/page-size/content-start-position.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C,KAAK,UAAU,KAAK,CAAC,IAAkB;IACrC,OAAO;QACL,EAAE,EAAE,wBAAwB;QAC5B,QAAQ,EAAE,WAAW;QACrB,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,qBAAqB;KAC/B,CAAC;AACJ,CAAC;AAED,aAAa,CAAC;IACZ,EAAE,EAAE,wBAAwB;IAC5B,QAAQ,EAAE,WAAW;IACrB,WAAW,EAAE,sDAAsD;IACnE,SAAS,EAAE,EAAE;IACb,GAAG,EAAE,KAAK;CACX,CAAC,CAAC"}
1
+ {"version":3,"file":"content-start-position.js","sourceRoot":"","sources":["../../../src/checks/page-size/content-start-position.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,132 @@
1
1
  import { registerCheck } from '../registry.js';
2
- async function check(_ctx) {
2
+ import { looksLikeHtml } from '../../helpers/detect-markdown.js';
3
+ import { discoverAndSamplePages } from '../../helpers/get-page-urls.js';
4
+ import { htmlToMarkdown } from '../../helpers/html-to-markdown.js';
5
+ function sizeStatus(chars, pass, fail) {
6
+ if (chars <= pass)
7
+ return 'pass';
8
+ if (chars <= fail)
9
+ return 'warn';
10
+ return 'fail';
11
+ }
12
+ function worstStatus(statuses) {
13
+ if (statuses.includes('fail'))
14
+ return 'fail';
15
+ if (statuses.includes('warn'))
16
+ return 'warn';
17
+ return 'pass';
18
+ }
19
+ function formatSize(chars) {
20
+ if (chars >= 1000)
21
+ return `${Math.round(chars / 1000)}K`;
22
+ return String(chars);
23
+ }
24
+ async function check(ctx) {
25
+ const id = 'page-size-html';
26
+ const category = 'page-size';
27
+ const { pass: passThreshold, fail: failThreshold } = ctx.options.thresholds;
28
+ const { urls: pageUrls, totalPages, sampled: wasSampled, warnings, } = await discoverAndSamplePages(ctx);
29
+ const results = [];
30
+ const concurrency = ctx.options.maxConcurrency;
31
+ for (let i = 0; i < pageUrls.length; i += concurrency) {
32
+ const batch = pageUrls.slice(i, i + concurrency);
33
+ const batchResults = await Promise.all(batch.map(async (url) => {
34
+ try {
35
+ const response = await ctx.http.fetch(url);
36
+ const body = await response.text();
37
+ const contentType = response.headers.get('content-type') ?? '';
38
+ const isMarkdownType = contentType.includes('text/markdown') || contentType.includes('text/plain');
39
+ const isHtml = !isMarkdownType && (contentType.includes('text/html') || looksLikeHtml(body));
40
+ // Skip conversion if the response is already markdown
41
+ const html = isHtml ? body : '';
42
+ const htmlChars = html.length;
43
+ const converted = isHtml ? htmlToMarkdown(body) : body;
44
+ const convertedChars = converted.length;
45
+ const ratio = htmlChars > 0 ? Math.round((1 - convertedChars / htmlChars) * 100) : 0;
46
+ return {
47
+ url,
48
+ htmlCharacters: htmlChars,
49
+ convertedCharacters: convertedChars,
50
+ conversionRatio: ratio,
51
+ status: sizeStatus(convertedChars, passThreshold, failThreshold),
52
+ };
53
+ }
54
+ catch (err) {
55
+ return {
56
+ url,
57
+ htmlCharacters: 0,
58
+ convertedCharacters: 0,
59
+ conversionRatio: 0,
60
+ status: 'fail',
61
+ error: err instanceof Error ? err.message : String(err),
62
+ };
63
+ }
64
+ }));
65
+ results.push(...batchResults);
66
+ }
67
+ const successful = results.filter((r) => !r.error);
68
+ const fetchErrors = results.filter((r) => r.error).length;
69
+ const rateLimited = results.filter((r) => r.error && r.error.includes('429')).length;
70
+ if (successful.length === 0) {
71
+ const suffix = fetchErrors > 0 ? `; ${fetchErrors} failed to fetch` : '';
72
+ return {
73
+ id,
74
+ category,
75
+ status: 'fail',
76
+ message: `Could not fetch any pages to measure${suffix}`,
77
+ details: {
78
+ totalPages,
79
+ testedPages: results.length,
80
+ sampled: wasSampled,
81
+ fetchErrors,
82
+ rateLimited,
83
+ pageResults: results,
84
+ discoveryWarnings: warnings,
85
+ },
86
+ };
87
+ }
88
+ const convertedSizes = successful.map((r) => r.convertedCharacters).sort((a, b) => a - b);
89
+ const median = convertedSizes[Math.floor(convertedSizes.length / 2)];
90
+ const max = convertedSizes[convertedSizes.length - 1];
91
+ const avgRatio = Math.round(successful.reduce((sum, r) => sum + r.conversionRatio, 0) / successful.length);
92
+ const overallStatus = worstStatus(successful.map((r) => r.status));
93
+ const pageLabel = wasSampled ? 'sampled pages' : 'pages';
94
+ const passBucket = successful.filter((r) => r.status === 'pass').length;
95
+ const warnBucket = successful.filter((r) => r.status === 'warn').length;
96
+ const failBucket = successful.filter((r) => r.status === 'fail').length;
97
+ const suffix = (fetchErrors > 0 ? `; ${fetchErrors} failed to fetch` : '') +
98
+ (rateLimited > 0 ? `; ${rateLimited} rate-limited (HTTP 429)` : '');
99
+ let message;
100
+ if (overallStatus === 'pass') {
101
+ message = `All ${successful.length} ${pageLabel} convert under ${formatSize(passThreshold)} chars (median ${formatSize(median)}, ${avgRatio}% boilerplate)${suffix}`;
102
+ }
103
+ else if (overallStatus === 'warn') {
104
+ message = `${warnBucket} of ${successful.length} ${pageLabel} convert to ${formatSize(passThreshold)}–${formatSize(failThreshold)} chars (max ${formatSize(max)}, ${avgRatio}% boilerplate)${suffix}`;
105
+ }
106
+ else {
107
+ message = `${failBucket} of ${successful.length} ${pageLabel} convert to over ${formatSize(failThreshold)} chars (max ${formatSize(max)}, ${avgRatio}% boilerplate)${suffix}`;
108
+ }
3
109
  return {
4
- id: 'page-size-html',
5
- category: 'page-size',
6
- status: 'skip',
7
- message: 'Not yet implemented',
110
+ id,
111
+ category,
112
+ status: overallStatus,
113
+ message,
114
+ details: {
115
+ totalPages,
116
+ testedPages: results.length,
117
+ sampled: wasSampled,
118
+ median,
119
+ max,
120
+ avgBoilerplatePercent: avgRatio,
121
+ passBucket,
122
+ warnBucket,
123
+ failBucket,
124
+ fetchErrors,
125
+ rateLimited,
126
+ thresholds: { pass: passThreshold, fail: failThreshold },
127
+ pageResults: results,
128
+ discoveryWarnings: warnings,
129
+ },
8
130
  };
9
131
  }
10
132
  registerCheck({
@@ -1 +1 @@
1
- {"version":3,"file":"page-size-html.js","sourceRoot":"","sources":["../../../src/checks/page-size/page-size-html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C,KAAK,UAAU,KAAK,CAAC,IAAkB;IACrC,OAAO;QACL,EAAE,EAAE,gBAAgB;QACpB,QAAQ,EAAE,WAAW;QACrB,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,qBAAqB;KAC/B,CAAC;AACJ,CAAC;AAED,aAAa,CAAC;IACZ,EAAE,EAAE,gBAAgB;IACpB,QAAQ,EAAE,WAAW;IACrB,WAAW,EAAE,2DAA2D;IACxE,SAAS,EAAE,EAAE;IACb,GAAG,EAAE,KAAK;CACX,CAAC,CAAC"}
1
+ {"version":3,"file":"page-size-html.js","sourceRoot":"","sources":["../../../src/checks/page-size/page-size-html.ts"],"names":[],"mappings":"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"}