@j0hanz/fetch-url-mcp 1.10.21 → 1.10.22

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.
@@ -35,6 +35,9 @@ const MAX_LINE_LENGTH = 80;
35
35
  const TOC_SCAN_LIMIT = 20;
36
36
  const TOC_MAX_NON_EMPTY = 12;
37
37
  const TOC_LINK_RATIO_THRESHOLD = 0.8;
38
+ // ── List indentation normalization ───────────────────────────────────
39
+ const SOURCE_INDENT_STEP = 2;
40
+ const TARGET_INDENT_STEP = 4;
38
41
  // ── Docs-chrome scan depth ───────────────────────────────────────────
39
42
  const CHROME_SCAN_LINE_LIMIT = 12;
40
43
  // ── Fence pattern ───────────────────────────────────────────────────
@@ -52,6 +55,9 @@ const REGEX = {
52
55
  TOC_HEADING: /^(?:#{1,6}\s+)?(?:table of contents|contents|on this page)\s*$/i,
53
56
  COMBINED_LINE_REMOVALS: /^(?:\[Skip to (?:main )?(?:content|navigation)\]\(#[^)]*\)|\[Skip link\]\(#[^)]*\)|Was this page helpful\??|\[Back to top\]\(#[^)]*\)|\[\s*\]\(https?:\/\/[^)]*\))\s*$/gim,
54
57
  ZERO_WIDTH_ANCHOR: /\[(?:\s|\u200B)*\]\(#[^)]*\)[ \t]*/g,
58
+ // ReDoS-safe: {0,30} bounds identifier backtracking, negated char class
59
+ // [^\u0022\u201C\u201D]* has no overlap with delimiters, and \s+ is anchored
60
+ // between ':' and a quote. Multi-pass capped at PROPERTY_FIX_MAX_PASSES.
55
61
  CONCATENATED_PROPS: /([a-z_][a-z0-9_]{0,30}\??:\s+)([\u0022\u201C][^\u0022\u201C\u201D]*[\u0022\u201D])([a-z_][a-z0-9_]{0,30}\??:)/g,
56
62
  DOUBLE_NEWLINE_REDUCER: /\n{3,}/g,
57
63
  HEADING_SPACING: /(^#{1,6}\s[^\n]*)\n([^\n])/gm,
@@ -101,13 +107,15 @@ function hasFollowingContent(lines, startIndex) {
101
107
  }
102
108
  return false;
103
109
  }
104
- function findNextNonBlankLine(lines, startIndex) {
105
- for (let i = startIndex + 1; i < Math.min(lines.length, startIndex + HAS_FOLLOWING_LOOKAHEAD); i++) {
106
- const line = lines[i];
107
- if (!isBlank(line))
108
- return line?.trim();
110
+ function findNextNonBlank(lines, startIndex, maxLookahead) {
111
+ const limit = maxLookahead !== undefined
112
+ ? Math.min(lines.length, startIndex + maxLookahead)
113
+ : lines.length;
114
+ for (let i = startIndex; i < limit; i++) {
115
+ if (!isBlank(lines[i]))
116
+ return i;
109
117
  }
110
- return undefined;
118
+ return -1;
111
119
  }
112
120
  function stripAnchorOnlyHeading(line) {
113
121
  return line.replace(/^(#{1,6})\s+\[([^\]]+)\]\(#[^)]+\)\s*$/, '$1 $2');
@@ -140,33 +148,37 @@ function isTitleCaseOrKeyword(trimmed) {
140
148
  }
141
149
  return capitalizedCount >= TITLE_MIN_CAPITALIZED;
142
150
  }
151
+ function isMarkdownStructuralLine(trimmed) {
152
+ const firstChar = trimmed.charCodeAt(0);
153
+ if (firstChar !== ASCII_MARKERS.HASH &&
154
+ firstChar !== ASCII_MARKERS.DASH &&
155
+ firstChar !== ASCII_MARKERS.ASTERISK &&
156
+ firstChar !== ASCII_MARKERS.PLUS &&
157
+ firstChar !== ASCII_MARKERS.BRACKET_OPEN &&
158
+ (firstChar < ASCII_MARKERS.DIGIT_0 || firstChar > ASCII_MARKERS.DIGIT_9)) {
159
+ return false;
160
+ }
161
+ return (REGEX.HEADING_MARKER.test(trimmed) ||
162
+ REGEX.LIST_MARKER.test(trimmed) ||
163
+ /^\d+\.\s/.test(trimmed) ||
164
+ /^\[.*\]\(.*\)$/.test(trimmed));
165
+ }
166
+ function isTerminalPunctuation(charCode) {
167
+ return (charCode === ASCII_MARKERS.PERIOD ||
168
+ charCode === ASCII_MARKERS.EXCLAMATION ||
169
+ charCode === ASCII_MARKERS.QUESTION);
170
+ }
143
171
  function getHeadingPrefix(trimmed) {
144
172
  if (trimmed.length > MAX_LINE_LENGTH)
145
173
  return null;
146
174
  if (REPL_PROMPT_LINE.test(trimmed))
147
175
  return null;
148
- // Fast path: Check common markdown markers first
149
- const firstChar = trimmed.charCodeAt(0);
150
- if (firstChar === ASCII_MARKERS.HASH ||
151
- firstChar === ASCII_MARKERS.DASH ||
152
- firstChar === ASCII_MARKERS.ASTERISK ||
153
- firstChar === ASCII_MARKERS.PLUS ||
154
- firstChar === ASCII_MARKERS.BRACKET_OPEN ||
155
- (firstChar >= ASCII_MARKERS.DIGIT_0 && firstChar <= ASCII_MARKERS.DIGIT_9)) {
156
- if (REGEX.HEADING_MARKER.test(trimmed) ||
157
- REGEX.LIST_MARKER.test(trimmed) ||
158
- /^\d+\.\s/.test(trimmed) ||
159
- /^\[.*\]\(.*\)$/.test(trimmed)) {
160
- return null;
161
- }
162
- }
176
+ if (isMarkdownStructuralLine(trimmed))
177
+ return null;
163
178
  if (SPECIAL_PREFIXES.test(trimmed)) {
164
179
  return /^example:\s/i.test(trimmed) ? '### ' : '## ';
165
180
  }
166
- const lastChar = trimmed.charCodeAt(trimmed.length - 1);
167
- if (lastChar === ASCII_MARKERS.PERIOD ||
168
- lastChar === ASCII_MARKERS.EXCLAMATION ||
169
- lastChar === ASCII_MARKERS.QUESTION)
181
+ if (isTerminalPunctuation(trimmed.charCodeAt(trimmed.length - 1)))
170
182
  return null;
171
183
  return isTitleCaseOrKeyword(trimmed) ? '## ' : null;
172
184
  }
@@ -219,57 +231,59 @@ function isTypeDocArtifactLine(line) {
219
231
  }
220
232
  return false;
221
233
  }
222
- function tryPromoteOrphan(lines, i, trimmed) {
223
- const prevLine = lines[i - 1];
224
- const isOrphan = i === 0 || !prevLine || prevLine.trim().length === 0;
234
+ function tryPromoteOrphan(ctx) {
235
+ const prevLine = ctx.lines[ctx.index - 1];
236
+ const isOrphan = ctx.index === 0 || !prevLine || prevLine.trim().length === 0;
225
237
  if (!isOrphan)
226
238
  return null;
227
- const prefix = getHeadingPrefix(trimmed);
239
+ const prefix = getHeadingPrefix(ctx.trimmed);
228
240
  if (!prefix)
229
241
  return null;
230
- const isSpecialPrefix = SPECIAL_PREFIXES.test(trimmed);
231
- if (!isSpecialPrefix && !hasFollowingContent(lines, i))
242
+ const isSpecialPrefix = SPECIAL_PREFIXES.test(ctx.trimmed);
243
+ if (!isSpecialPrefix && !hasFollowingContent(ctx.lines, ctx.index))
232
244
  return null;
233
245
  if (!isSpecialPrefix) {
234
- const nextLine = findNextNonBlankLine(lines, i);
246
+ const nextIdx = findNextNonBlank(ctx.lines, ctx.index + 1, HAS_FOLLOWING_LOOKAHEAD);
247
+ const nextLine = nextIdx >= 0 ? ctx.lines[nextIdx]?.trim() : undefined;
235
248
  if (nextLine && REGEX.HEADING_MARKER.test(nextLine))
236
249
  return null;
237
250
  }
238
- return `${prefix}${trimmed}`;
251
+ return `${prefix}${ctx.trimmed}`;
239
252
  }
240
- function shouldSkipAsToc(lines, i, trimmed, removeToc, options) {
241
- if (!removeToc || !REGEX.TOC_HEADING.test(trimmed))
253
+ function shouldSkipAsToc(ctx, removeToc, options) {
254
+ if (!removeToc || !REGEX.TOC_HEADING.test(ctx.trimmed))
242
255
  return null;
243
- const { total, linkCount, nonLinkCount } = getTocBlockStats(lines, i);
256
+ const { total, linkCount, nonLinkCount } = getTocBlockStats(ctx.lines, ctx.index);
244
257
  if (total === 0 || nonLinkCount > 0)
245
258
  return null;
246
259
  const ratio = linkCount / total;
247
260
  if (ratio <= TOC_LINK_RATIO_THRESHOLD)
248
261
  return null;
249
262
  throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:toc');
250
- return skipTocLines(lines, i + 1);
263
+ return skipTocLines(ctx.lines, ctx.index + 1);
251
264
  }
252
- function normalizePreprocessLine(lines, i, trimmed, line, options) {
253
- if (REGEX.EMPTY_HEADING_LINE.test(trimmed))
265
+ function normalizePreprocessLine(ctx, options) {
266
+ if (REGEX.EMPTY_HEADING_LINE.test(ctx.trimmed))
254
267
  return null;
255
- if (!REGEX.ANCHOR_ONLY_HEADING.test(trimmed))
256
- return line;
257
- if (!hasFollowingContent(lines, i)) {
268
+ if (!REGEX.ANCHOR_ONLY_HEADING.test(ctx.trimmed))
269
+ return ctx.line;
270
+ if (!hasFollowingContent(ctx.lines, ctx.index)) {
258
271
  return options?.preserveEmptyHeadings
259
- ? stripAnchorOnlyHeading(trimmed)
272
+ ? stripAnchorOnlyHeading(ctx.trimmed)
260
273
  : null;
261
274
  }
262
- return stripAnchorOnlyHeading(trimmed);
275
+ return stripAnchorOnlyHeading(ctx.trimmed);
263
276
  }
264
- function maybeSkipTocBlock(lines, i, trimmed, options) {
265
- return shouldSkipAsToc(lines, i, trimmed, config.markdownCleanup.removeTocBlocks, options);
277
+ function maybeSkipTocBlock(ctx, options) {
278
+ return shouldSkipAsToc(ctx, config.markdownCleanup.removeTocBlocks, options);
266
279
  }
267
- function maybePromoteOrphanHeading(lines, i, trimmed, checkAbort) {
268
- if (!config.markdownCleanup.promoteOrphanHeadings || trimmed.length === 0) {
280
+ function maybePromoteOrphanHeading(ctx, checkAbort) {
281
+ if (!config.markdownCleanup.promoteOrphanHeadings ||
282
+ ctx.trimmed.length === 0) {
269
283
  return null;
270
284
  }
271
285
  checkAbort('markdown:cleanup:promote');
272
- return tryPromoteOrphan(lines, i, trimmed);
286
+ return tryPromoteOrphan(ctx);
273
287
  }
274
288
  function preprocessLines(lines, options) {
275
289
  const checkAbort = createAbortChecker(options);
@@ -280,15 +294,16 @@ function preprocessLines(lines, options) {
280
294
  continue;
281
295
  const currentLine = lines[i] ?? '';
282
296
  const trimmed = currentLine.trim();
283
- const normalizedLine = normalizePreprocessLine(lines, i, trimmed, currentLine, options);
297
+ const ctx = { lines, index: i, trimmed, line: currentLine };
298
+ const normalizedLine = normalizePreprocessLine(ctx, options);
284
299
  if (normalizedLine === null)
285
300
  continue;
286
- const tocSkip = maybeSkipTocBlock(lines, i, trimmed, options);
301
+ const tocSkip = maybeSkipTocBlock(ctx, options);
287
302
  if (tocSkip !== null) {
288
303
  skipUntil = tocSkip;
289
304
  continue;
290
305
  }
291
- const promotedLine = maybePromoteOrphanHeading(lines, i, trimmed, checkAbort);
306
+ const promotedLine = maybePromoteOrphanHeading(ctx, checkAbort);
292
307
  result.push(promotedLine ?? normalizedLine);
293
308
  }
294
309
  return result.join('\n');
@@ -369,13 +384,6 @@ function getHeadingInfo(line) {
369
384
  return null;
370
385
  return { level: match[1]?.length ?? 0 };
371
386
  }
372
- function findNextNonBlankIndex(lines, startIndex) {
373
- let idx = startIndex;
374
- while (idx < lines.length && isBlank(lines[idx])) {
375
- idx += 1;
376
- }
377
- return idx;
378
- }
379
387
  function removeEmptyHeadingSections(text) {
380
388
  const lines = text.split('\n');
381
389
  const kept = [];
@@ -386,8 +394,8 @@ function removeEmptyHeadingSections(text) {
386
394
  kept.push(line);
387
395
  continue;
388
396
  }
389
- const nextIndex = findNextNonBlankIndex(lines, i + 1);
390
- const nextLine = lines[nextIndex];
397
+ const nextIndex = findNextNonBlank(lines, i + 1);
398
+ const nextLine = nextIndex >= 0 ? lines[nextIndex] : undefined;
391
399
  if (nextLine === undefined) {
392
400
  kept.push(line);
393
401
  continue;
@@ -482,9 +490,9 @@ function applyGlobalRegexes(text, options) {
482
490
  function normalizeNestedListIndentation(text) {
483
491
  return text.replace(REGEX.NESTED_LIST_INDENT, (match, spaces, marker) => {
484
492
  const count = spaces.length;
485
- if (count < 2 || count % 2 !== 0)
493
+ if (count < SOURCE_INDENT_STEP || count % SOURCE_INDENT_STEP !== 0)
486
494
  return match;
487
- const normalized = ' '.repeat((count / 2) * 4);
495
+ const normalized = ' '.repeat((count / SOURCE_INDENT_STEP) * TARGET_INDENT_STEP);
488
496
  return `${normalized}${marker} `;
489
497
  });
490
498
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/transform/transform.ts"],"names":[],"mappings":"AAyCA,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAqB/B,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AA+BpB,UAAU,WAAW;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAgJD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GACnB,qBAAqB,GAAG,IAAI,CAE9B;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,qBAAqB,GAAG,IAAI,EACrC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,MAAM,CAER;AA8XD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAExD,GACA,gBAAgB,CAGlB;AAyKD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC,GACA,MAAM,CAyBR;AA2DD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,sBAAsB,EAAE,MAAM,GAAG,QAAQ,GACxC,OAAO,CAQT;AAKD,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAC/B,OAAO,IAAI,gBAAgB,CAE7B;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,aAAa,EAAE,iBAAiB,EAChC,wBAAwB,EAAE,OAAO,EACjC,eAAe,EAAE,OAAO,GACvB,aAAa,GAAG,SAAS,CAuB3B;AA4bD,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GACxB,uBAAuB,CAgBzB;AAaD,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,qBAAqB,IAAI,kBAAkB,GAAG,IAAI,CAEjE;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAEjE;AAED,KAAK,yBAAyB,GAAG,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAgH1E,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,uBAAuB,CAAC,CAElC;AAED,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,GACrB,CAAC"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/transform/transform.ts"],"names":[],"mappings":"AA0CA,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAqB/B,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AA+BpB,UAAU,WAAW;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AA4LD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GACnB,qBAAqB,GAAG,IAAI,CAE9B;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,qBAAqB,GAAG,IAAI,EACrC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,MAAM,CAER;AA2aD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAExD,GACA,gBAAgB,CAGlB;AAuKD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC,GACA,MAAM,CAyBR;AA2DD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,sBAAsB,EAAE,MAAM,GAAG,QAAQ,GACxC,OAAO,CAQT;AAKD,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,gBAAgB,GAAG,IAAI,GAC/B,OAAO,IAAI,gBAAgB,CAE7B;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAChC,aAAa,EAAE,iBAAiB,EAChC,wBAAwB,EAAE,OAAO,EACjC,eAAe,EAAE,OAAO,GACvB,aAAa,GAAG,SAAS,CAuB3B;AA8bD,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GACxB,uBAAuB,CAMzB;AAaD,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,qBAAqB,IAAI,kBAAkB,GAAG,IAAI,CAEjE;AAED,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAEjE;AAED,KAAK,yBAAyB,GAAG,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAuG1E,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,uBAAuB,CAAC,CAElC;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,uBAAuB,CAAC,CAElC;AAED,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,GACrB,CAAC"}
@@ -3,7 +3,7 @@ import { isProbablyReaderable, Readability } from '@mozilla/readability';
3
3
  import { parseHTML } from 'linkedom';
4
4
  import { config } from '../lib/core.js';
5
5
  import { getOperationId, getRequestId, logDebug, logError, logInfo, logWarn, redactUrl, } from '../lib/core.js';
6
- import { evaluateArticleContent, extractNoscriptImages, getVisibleTextLength, normalizeTabContent, prepareDocumentForMarkdown, removeNoiseFromHtml, serializeDocumentForMarkdown, surfaceCodeEditorContent, } from '../lib/dom-prep.js';
6
+ import { evaluateArticleContent, extractNoscriptImages, getVisibleTextLength, normalizeTabContent, prepareDocumentForMarkdown, removeNoiseFromHtml, serializeDocumentForMarkdown, stripDocsControls, surfaceCodeEditorContent, } from '../lib/dom-prep.js';
7
7
  import { isRawTextContentUrl } from '../lib/http.js';
8
8
  import { composeAbortSignal, FetchError, getErrorMessage, getUtf8ByteLength, isAsciiOnly, isObject, throwIfAborted, toError, trimDanglingTagFragment, truncateToUtf8Boundary, } from '../lib/utils.js';
9
9
  import { extractLanguageFromClassName } from './html-translators.js';
@@ -139,6 +139,36 @@ class StageTracker {
139
139
  });
140
140
  }
141
141
  }
142
+ runTrackedSync(url, signal, fn) {
143
+ const totalStage = this.start(url, 'transform:total');
144
+ try {
145
+ throwIfAborted(signal, url, 'transform:begin');
146
+ const result = fn();
147
+ this.end(totalStage, result.truncated !== undefined
148
+ ? { truncated: result.truncated }
149
+ : undefined);
150
+ return result;
151
+ }
152
+ catch (error) {
153
+ this.end(totalStage);
154
+ throw error;
155
+ }
156
+ }
157
+ async runTrackedAsync(url, signal, fn) {
158
+ const totalStage = this.start(url, 'transform:total');
159
+ try {
160
+ throwIfAborted(signal, url, 'transform:begin');
161
+ const result = await fn();
162
+ this.end(totalStage, result.truncated !== undefined
163
+ ? { truncated: result.truncated }
164
+ : undefined);
165
+ return result;
166
+ }
167
+ catch (error) {
168
+ this.end(totalStage);
169
+ throw error;
170
+ }
171
+ }
142
172
  }
143
173
  const stageTracker = new StageTracker();
144
174
  export function startTransformStage(url, stage, budget) {
@@ -148,7 +178,7 @@ export function endTransformStage(context, options) {
148
178
  return stageTracker.end(context, options);
149
179
  }
150
180
  function truncateHtml(html, inputTruncated = false) {
151
- const maxSize = config.constants.maxHtmlSize;
181
+ const maxSize = config.constants.maxHtmlBytes;
152
182
  if (maxSize <= 0)
153
183
  return { html, truncated: false };
154
184
  if (html.length <= maxSize) {
@@ -182,7 +212,7 @@ function isReadabilityCompatible(doc) {
182
212
  'function' &&
183
213
  typeof record.querySelector === 'function');
184
214
  }
185
- function resolveCollapsedTextLengthUpTo(text, max) {
215
+ function getNormalizedTextLengthUpTo(text, max) {
186
216
  if (max <= 0)
187
217
  return 0;
188
218
  let length = 0;
@@ -234,6 +264,45 @@ function preserveAlertElements(doc) {
234
264
  el.replaceWith(bq);
235
265
  }
236
266
  }
267
+ function preserveHeadingLayouts(doc) {
268
+ // Readability aggressively drops elements matching /header/i in their class/id.
269
+ // Many technical docs use `<div class="layout__header">` to wrap their title and intro text,
270
+ // causing the ENTIRE intro and H1 to be dropped.
271
+ for (const heading of doc.querySelectorAll('h1, h2')) {
272
+ let p = heading.parentNode;
273
+ while (p && p.tagName !== 'BODY' && p.tagName !== 'HTML') {
274
+ const cls = p.getAttribute('class');
275
+ if (cls && /header/i.test(cls)) {
276
+ p.setAttribute('class', cls.replace(/header/gi, 'hdr-preserved'));
277
+ }
278
+ const id = p.getAttribute('id');
279
+ if (id && /header/i.test(id)) {
280
+ p.setAttribute('id', id.replace(/header/gi, 'hdr-preserved'));
281
+ }
282
+ p = p.parentNode;
283
+ }
284
+ }
285
+ // To prevent Readability from penalizing sibling document sections
286
+ // (e.g. intro vs reference tables) and picking only one, we unwrap structural wrappers inside main boundaries.
287
+ for (const main of doc.querySelectorAll('main, [role="main"], article')) {
288
+ for (const child of Array.from(main.children)) {
289
+ // Don't unwrap nav, aside, or blockquotes (alerts are already converted to blockquotes here)
290
+ if (child.tagName === 'DIV' ||
291
+ child.tagName === 'HEADER' ||
292
+ child.tagName === 'SECTION') {
293
+ // preserve specific structural features Readability might want to keep
294
+ const cls = child.getAttribute('class') ?? '';
295
+ if (cls.includes('mermaid'))
296
+ continue;
297
+ const frag = doc.createDocumentFragment();
298
+ while (child.firstChild) {
299
+ frag.appendChild(child.firstChild);
300
+ }
301
+ child.replaceWith(frag);
302
+ }
303
+ }
304
+ }
305
+ }
237
306
  function preserveCodeLanguageAttributes(doc) {
238
307
  for (const el of doc.querySelectorAll('pre, code')) {
239
308
  if (el.getAttribute('data-language'))
@@ -247,9 +316,11 @@ function prepareReadabilityDocument(readabilityDoc) {
247
316
  extractNoscriptImages(readabilityDoc);
248
317
  preserveGalleryImages(readabilityDoc);
249
318
  preserveAlertElements(readabilityDoc);
319
+ preserveHeadingLayouts(readabilityDoc);
250
320
  preserveCodeLanguageAttributes(readabilityDoc);
251
321
  normalizeTabContent(readabilityDoc);
252
322
  surfaceCodeEditorContent(readabilityDoc);
323
+ stripDocsControls(readabilityDoc);
253
324
  for (const el of readabilityDoc.querySelectorAll('[class*="breadcrumb"],[class*="pagination"]')) {
254
325
  if (el.tagName === 'HTML' || el.tagName === 'BODY')
255
326
  continue;
@@ -261,7 +332,7 @@ function validateReaderability(doc, url, signal) {
261
332
  const rawText = doc.querySelector('body')?.textContent ??
262
333
  doc.documentElement.textContent ??
263
334
  '';
264
- const textLength = resolveCollapsedTextLengthUpTo(rawText, MIN_READERABLE_TEXT_LENGTH + 1);
335
+ const textLength = getNormalizedTextLengthUpTo(rawText, MIN_READERABLE_TEXT_LENGTH + 1);
265
336
  if (textLength < MIN_SPA_CONTENT_LENGTH) {
266
337
  logWarn('Very minimal server-rendered content detected (< 100 chars). ' +
267
338
  'This might be a client-side rendered (SPA) application. ' +
@@ -360,7 +431,7 @@ function createEmptyExtractionContext() {
360
431
  return { article: null, metadata: {}, document };
361
432
  }
362
433
  function extractEarlyMetadataIfNeeded(html, url) {
363
- const maxSize = config.constants.maxHtmlSize;
434
+ const maxSize = config.constants.maxHtmlBytes;
364
435
  if (maxSize <= 0)
365
436
  return null;
366
437
  if (html.length <= maxSize &&
@@ -447,27 +518,26 @@ function findBalancedCloseParen(text, start) {
447
518
  return -1;
448
519
  }
449
520
  function findInlineLink(markdown, start) {
450
- let searchFrom = start;
451
- while (searchFrom < markdown.length) {
452
- const openBracket = markdown.indexOf('[', searchFrom);
453
- if (openBracket === -1)
454
- return null;
521
+ let openBracket = markdown.indexOf('[', start);
522
+ while (openBracket !== -1) {
455
523
  const closeBracket = markdown.indexOf(']', openBracket + 1);
456
524
  if (closeBracket === -1)
457
525
  return null;
458
526
  if (markdown[closeBracket + 1] !== '(') {
459
- searchFrom = closeBracket + 1;
527
+ openBracket = markdown.indexOf('[', closeBracket + 1);
460
528
  continue;
461
529
  }
462
530
  const closeParen = findBalancedCloseParen(markdown, closeBracket + 2);
463
531
  if (closeParen === -1)
464
532
  return null;
465
- const prefixStart = openBracket > 0 && markdown[openBracket - 1] === '!'
466
- ? openBracket - 1
467
- : openBracket;
468
- const prefix = markdown.slice(prefixStart, closeBracket + 1);
469
- const href = markdown.slice(closeBracket + 2, closeParen);
470
- return { prefixStart, closeParen, prefix, href };
533
+ const isImage = openBracket > 0 && markdown[openBracket - 1] === '!';
534
+ const prefixStart = isImage ? openBracket - 1 : openBracket;
535
+ return {
536
+ prefixStart,
537
+ closeParen,
538
+ prefix: markdown.slice(prefixStart, closeBracket + 1),
539
+ href: markdown.slice(closeBracket + 2, closeParen),
540
+ };
471
541
  }
472
542
  return null;
473
543
  }
@@ -646,7 +716,7 @@ function resolveContentTitle(params) {
646
716
  normalizeSyntheticTitleToken(params.primaryHeading),
647
717
  };
648
718
  }
649
- const CONTENT_ROOT_SELECTORS = [
719
+ const CONTENT_REGION_SELECTORS = [
650
720
  'article',
651
721
  'main',
652
722
  '[role="main"]',
@@ -662,14 +732,12 @@ const CONTENT_ROOT_SELECTORS = [
662
732
  '.post-body',
663
733
  '.article-body',
664
734
  ];
665
- const PRIMARY_HEADING_ROOT_SELECTORS = [
666
- ...CONTENT_ROOT_SELECTORS,
735
+ const HEADING_REGION_EXTRA_SELECTORS = [
667
736
  '.markdown-body',
668
- '.entry-content',
669
737
  '[itemprop="text"]',
670
738
  ];
671
739
  function findContentRoot(document) {
672
- for (const selector of CONTENT_ROOT_SELECTORS) {
740
+ for (const selector of CONTENT_REGION_SELECTORS) {
673
741
  const element = document.querySelector(selector);
674
742
  if (!element)
675
743
  continue;
@@ -702,7 +770,10 @@ function findPrimaryHeading(document) {
702
770
  const globalHeading = extractHeadingText(document, PRIMARY_HEADING_SELECTORS_GLOBAL);
703
771
  if (globalHeading)
704
772
  return globalHeading;
705
- for (const selector of PRIMARY_HEADING_ROOT_SELECTORS) {
773
+ for (const selector of [
774
+ ...CONTENT_REGION_SELECTORS,
775
+ ...HEADING_REGION_EXTRA_SELECTORS,
776
+ ]) {
706
777
  const root = document.querySelector(selector);
707
778
  if (!root)
708
779
  continue;
@@ -759,39 +830,44 @@ function buildRawSource(base, params) {
759
830
  title: params.extractedMeta.title,
760
831
  };
761
832
  }
762
- function buildContentSource(params) {
763
- const { html, url, article, extractedMeta, includeMetadata, evaluatedArticleDoc, document, truncated, signal, } = params;
764
- const useArticleContent = evaluatedArticleDoc !== null;
765
- const metadata = createContentMetadataBlock(url, article, extractedMeta, useArticleContent, includeMetadata);
833
+ function resolveBaseContentSource(input) {
834
+ const { html, url, article, extractedMeta, includeMetadata, evaluatedArticleDoc, document, truncated, signal, } = input;
835
+ const metadata = createContentMetadataBlock(url, article, extractedMeta, evaluatedArticleDoc !== null, includeMetadata);
766
836
  const preparedDocument = document
767
837
  ? prepareContentSourceDocument(document, url, signal)
768
838
  : undefined;
769
- const primaryHeading = preparedDocument?.primaryHeading;
770
839
  const base = {
771
840
  favicon: extractedMeta.favicon,
772
841
  metadata,
773
842
  extractedMetadata: extractedMeta,
774
843
  truncated,
775
- primaryHeading,
844
+ primaryHeading: preparedDocument?.primaryHeading,
776
845
  originalHtml: html,
777
846
  };
778
- if (evaluatedArticleDoc && article) {
847
+ return { base, preparedDocument };
848
+ }
849
+ function buildContentSource(input) {
850
+ const { base, preparedDocument } = resolveBaseContentSource(input);
851
+ if (input.evaluatedArticleDoc && input.article) {
779
852
  return buildArticleSource(base, {
780
- evaluatedArticleDoc,
781
- article,
782
- extractedMeta,
783
- url,
784
- signal,
853
+ evaluatedArticleDoc: input.evaluatedArticleDoc,
854
+ article: input.article,
855
+ extractedMeta: input.extractedMeta,
856
+ url: input.url,
857
+ signal: input.signal,
785
858
  });
786
859
  }
787
860
  if (preparedDocument) {
788
861
  return buildDocumentSource(base, {
789
862
  resolvedDocument: preparedDocument.document,
790
- html,
791
- extractedMeta,
863
+ html: input.html,
864
+ extractedMeta: input.extractedMeta,
792
865
  });
793
866
  }
794
- return buildRawSource(base, { html, extractedMeta });
867
+ return buildRawSource(base, {
868
+ html: input.html,
869
+ extractedMeta: input.extractedMeta,
870
+ });
795
871
  }
796
872
  function resolveContentSource(params) {
797
873
  const { article, metadata: extractedMeta, document, truncated, } = extractContentContext(params.html, params.url, {
@@ -857,10 +933,6 @@ function resolveTransformContentResult(html, url, options, signal) {
857
933
  }));
858
934
  return buildMarkdownFromContext(context, url, signal);
859
935
  }
860
- function completeTrackedTransform(totalStage, result) {
861
- stageTracker.end(totalStage, result.truncated !== undefined ? { truncated: result.truncated } : undefined);
862
- return result;
863
- }
864
936
  const REPLACEMENT_CHAR = '\ufffd';
865
937
  const BINARY_INDICATOR_THRESHOLD = 0.1;
866
938
  function hasBinaryIndicators(content) {
@@ -879,16 +951,10 @@ function hasBinaryIndicators(content) {
879
951
  }
880
952
  export function transformHtmlToMarkdownInProcess(html, url, options) {
881
953
  const signal = buildTransformSignal(options.signal);
882
- const totalStage = stageTracker.start(url, 'transform:total');
883
- try {
884
- throwIfAborted(signal, url, 'transform:begin');
954
+ return stageTracker.runTrackedSync(url, signal, () => {
885
955
  validateBinaryContent(html, url);
886
- return completeTrackedTransform(totalStage, resolveTransformContentResult(html, url, options, signal));
887
- }
888
- catch (error) {
889
- stageTracker.end(totalStage);
890
- throw error;
891
- }
956
+ return resolveTransformContentResult(html, url, options, signal);
957
+ });
892
958
  }
893
959
  function validateBinaryContent(html, url) {
894
960
  if (hasBinaryIndicators(html)) {
@@ -957,15 +1023,7 @@ async function runWorkerTransformWithFallback(htmlOrBuffer, url, options) {
957
1023
  });
958
1024
  }
959
1025
  async function transformInputToMarkdown(htmlOrBuffer, url, options) {
960
- const totalStage = stageTracker.start(url, 'transform:total');
961
- try {
962
- throwIfAborted(options.signal, url, 'transform:begin');
963
- return completeTrackedTransform(totalStage, await runWorkerTransformWithFallback(htmlOrBuffer, url, options));
964
- }
965
- catch (error) {
966
- stageTracker.end(totalStage);
967
- throw error;
968
- }
1026
+ return stageTracker.runTrackedAsync(url, options.signal, () => runWorkerTransformWithFallback(htmlOrBuffer, url, options));
969
1027
  }
970
1028
  export async function transformHtmlToMarkdown(html, url, options) {
971
1029
  return transformInputToMarkdown(html, url, options);
@@ -24,6 +24,8 @@ declare class WorkerPool implements TransformWorkerPool {
24
24
  private closed;
25
25
  private taskIdSeq;
26
26
  private busyCount;
27
+ private draining;
28
+ private readonly restartBackoff;
27
29
  constructor(size: number, timeoutMs: number);
28
30
  transform(html: string, url: string, options: {
29
31
  includeMetadata: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../src/transform/worker-pool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../src/transform/worker-pool.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EACV,uBAAuB,EAKxB,MAAM,YAAY,CAAC;AAqIpB,UAAU,mBAAmB;IAC3B,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACP,eAAe,EAAE,OAAO,CAAC;QACzB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,cAAc,CAAC,EAAE,OAAO,CAAC;KAC1B,GACA,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,aAAa,IAAI,MAAM,CAAC;IACxB,gBAAgB,IAAI,MAAM,CAAC;IAC3B,WAAW,IAAI,MAAM,CAAC;CACvB;AAmJD,cAAM,UAAW,YAAW,mBAAmB;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAkC;IAExE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAEhD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0B;IAErD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;gBAEhD,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IASrC,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACP,eAAe,EAAE,OAAO,CAAC;QACzB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,cAAc,CAAC,EAAE,OAAO,CAAC;KAC1B,GACA,OAAO,CAAC,uBAAuB,CAAC;IAC7B,SAAS,CACb,UAAU,EAAE,UAAU,EACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACP,eAAe,EAAE,OAAO,CAAC;QACzB,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GACA,OAAO,CAAC,uBAAuB,CAAC;IAyCnC,aAAa,IAAI,MAAM;IAIvB,gBAAgB,IAAI,MAAM;IAI1B,WAAW,IAAI,MAAM;IAIrB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAWpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+B5B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,iBAAiB;IAoDzB,OAAO,CAAC,aAAa;YA4BP,aAAa;IA2B3B,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,WAAW;IAmCnB,OAAO,CAAC,cAAc;IA6BtB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,mBAAmB;IA8B3B,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,UAAU;IAgClB,OAAO,CAAC,iBAAiB;IAoCzB,OAAO,CAAC,gBAAgB;IA8CxB,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,YAAY;CAGrB;AAMD,wBAAgB,qBAAqB,IAAI,UAAU,CAIlD;AAED,wBAAgB,kBAAkB,IAAI;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAOP;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIxD"}