@link-assistant/web-capture 1.12.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -1
  3. package/bin/web-capture.js +5 -7
  4. package/package.json +32 -18
  5. package/src/archive.js +1 -2
  6. package/src/binary-download.js +13 -0
  7. package/src/docx.js +14 -11
  8. package/src/fetch.js +1 -2
  9. package/src/figures.js +2 -12
  10. package/src/gdocs-images.js +0 -1
  11. package/src/gdocs.js +0 -1
  12. package/src/github.js +0 -1
  13. package/src/lib.js +1 -2
  14. package/src/localize-images.js +2 -12
  15. package/src/markdown.js +0 -1
  16. package/src/shared-dialog.js +0 -1
  17. package/src/stream.js +0 -1
  18. package/src/transport.js +23 -5
  19. package/src/txt.js +0 -1
  20. package/.changeset/README.md +0 -8
  21. package/.changeset/config.json +0 -11
  22. package/.dockerignore +0 -6
  23. package/.jscpd.json +0 -19
  24. package/.lenv +0 -8
  25. package/.prettierignore +0 -8
  26. package/.prettierrc +0 -10
  27. package/Dockerfile +0 -52
  28. package/babel.config.cjs +0 -13
  29. package/docker-compose.yml +0 -11
  30. package/eslint.config.js +0 -112
  31. package/examples/js/capture_cache.js +0 -12
  32. package/examples/js/engine_comparison.js +0 -39
  33. package/examples/js/html_download.js +0 -23
  34. package/examples/js/image_download.js +0 -36
  35. package/examples/js/markdown_download.js +0 -23
  36. package/examples/js/playwright_example.js +0 -23
  37. package/examples/python/html_download.py +0 -15
  38. package/examples/python/image_download.py +0 -20
  39. package/examples/python/markdown_download.py +0 -15
  40. package/examples/python/playwright_example.py +0 -22
  41. package/jest.config.mjs +0 -20
  42. package/tests/ci/dependency-freshness.test.js +0 -74
  43. package/tests/ci/detect-code-changes.test.js +0 -174
  44. package/tests/ci/docker-install.test.js +0 -31
  45. package/tests/ci/rust-dockerfile.test.js +0 -54
  46. package/tests/ci/workflow-policy.test.js +0 -79
  47. package/tests/e2e/docker.test.js +0 -186
  48. package/tests/e2e/process.test.js +0 -215
  49. package/tests/fixtures/bold-coalesce-and-leakage.html +0 -27
  50. package/tests/fixtures/deepwiki/deepwiki-full-page.png +0 -0
  51. package/tests/fixtures/deepwiki/index.md +0 -1500
  52. package/tests/fixtures/deepwiki/part-1.md +0 -776
  53. package/tests/fixtures/habr-articles.js +0 -45
  54. package/tests/fixtures/heading-numbering.html +0 -19
  55. package/tests/fixtures/list-item-with-br.html +0 -8
  56. package/tests/fixtures/ol-continuous-numbering.html +0 -12
  57. package/tests/fixtures/paragraph-vs-line-break.html +0 -11
  58. package/tests/helpers/nock.js +0 -10
  59. package/tests/helpers/search-fixture.cjs +0 -17
  60. package/tests/helpers/temporary-directory.js +0 -12
  61. package/tests/integration/api-endpoints.test.js +0 -418
  62. package/tests/integration/archive-default-layout.test.js +0 -37
  63. package/tests/integration/browser-engines.test.js +0 -128
  64. package/tests/integration/browser-packaged-path.test.js +0 -45
  65. package/tests/integration/deepwiki-download.test.js +0 -144
  66. package/tests/integration/deepwiki-rendered-page.mock.test.js +0 -121
  67. package/tests/integration/formalai-contract.test.js +0 -214
  68. package/tests/integration/gdocs-public-doc.test.js +0 -315
  69. package/tests/integration/github-readme.test.js +0 -137
  70. package/tests/integration/google-drive-image.test.js +0 -246
  71. package/tests/integration/habr-article.test.js +0 -337
  72. package/tests/integration/search-endpoint.test.js +0 -88
  73. package/tests/integration/stackoverflow-download.test.js +0 -173
  74. package/tests/integration/wikipedia-download.test.js +0 -142
  75. package/tests/jest.setup.mjs +0 -7
  76. package/tests/mock/index.test.js +0 -225
  77. package/tests/unit/animation.test.js +0 -33
  78. package/tests/unit/batch.test.js +0 -114
  79. package/tests/unit/bold-coalesce-and-leakage.test.js +0 -54
  80. package/tests/unit/browser-launch-options.test.js +0 -42
  81. package/tests/unit/browser.test.js +0 -172
  82. package/tests/unit/cache.test.js +0 -182
  83. package/tests/unit/cli.test.js +0 -399
  84. package/tests/unit/convertGoogleDriveUrl.test.js +0 -89
  85. package/tests/unit/convertRelativeUrls.test.js +0 -34
  86. package/tests/unit/extract-images.test.js +0 -259
  87. package/tests/unit/fetch-html.test.js +0 -54
  88. package/tests/unit/figures.test.js +0 -74
  89. package/tests/unit/gdocs-export-headers.test.js +0 -28
  90. package/tests/unit/gdocs-preprocess.test.js +0 -36
  91. package/tests/unit/gdocs.test.js +0 -1490
  92. package/tests/unit/github.test.js +0 -156
  93. package/tests/unit/heading-image.test.js +0 -49
  94. package/tests/unit/heading-numbering.test.js +0 -28
  95. package/tests/unit/html2md-br-in-list-item.test.js +0 -23
  96. package/tests/unit/html2md-ol-numbering.test.js +0 -20
  97. package/tests/unit/html2md.test.js +0 -695
  98. package/tests/unit/image-mode-defaults.test.js +0 -54
  99. package/tests/unit/kreuzberg.test.js +0 -249
  100. package/tests/unit/latex.test.js +0 -123
  101. package/tests/unit/localize-images.test.js +0 -114
  102. package/tests/unit/markdown.test.js +0 -36
  103. package/tests/unit/metadata.test.js +0 -140
  104. package/tests/unit/package-runtime-dependencies.test.js +0 -74
  105. package/tests/unit/paragraph-vs-line-break.test.js +0 -56
  106. package/tests/unit/postprocess.test.js +0 -110
  107. package/tests/unit/proxy-headers.test.js +0 -100
  108. package/tests/unit/publish-verification.test.js +0 -57
  109. package/tests/unit/retry.test.js +0 -111
  110. package/tests/unit/search.test.js +0 -339
  111. package/tests/unit/shared-dialog.test.js +0 -173
  112. package/tests/unit/stackoverflow.test.js +0 -32
  113. package/tests/unit/stream.test.js +0 -85
  114. package/tests/unit/verify.test.js +0 -182
  115. package/tests/xpaste/xpaste-screenshot-markdown.mock.test.js +0 -375
  116. package/tests/xpaste/xpaste.e2e.test.js +0 -36
  117. package/tests/xpaste/xpaste.integration.test.js +0 -41
  118. package/tests/xpaste/xpaste.mock.test.js +0 -261
  119. package/tests/xpaste/xpaste.unit.test.js +0 -119
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @link-assistant/web-capture
2
2
 
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 724c1a9: Fix CI/CD false positives, false negatives, and warnings (#177):
8
+
9
+ - Restore the `browser-commander` 0.10.0 pin required by open blocker #160 and repair the npm lock so `npm ci` installs again after the Dependabot update.
10
+ - Use Node's built-in `fetch` instead of `node-fetch`, removing the deprecated `node-domexception` package from the install. Transport diagnostics now include the underlying network error (for example `fetch failed: connect ECONNREFUSED ...`) and no longer report server-side network failures as possible CORS blocks.
11
+ - Load the `docx` library only when a DOCX is generated. Its bundled browser polyfills read `globalThis.localStorage` on import, so on Node 25+ every process that imported the server printed "ExperimentalWarning: localStorage is not available because --localstorage-file was not provided".
12
+ - Publish only the runtime files (`bin/`, `src/`, `README.md`, `CHANGELOG.md`) instead of the tests and fixtures, normalize `bin` and `repository` so `npm publish` no longer auto-corrects them, and install the pre-commit hook from the repository root instead of printing ".git can't be found" on every install.
13
+
14
+ ## 2.0.0
15
+
16
+ ### Major Changes
17
+
18
+ - b055a30: Require Node.js 26.10 or newer and update Playwright to 1.64. Use current Trixie browser images, native Jest ESM and reproducibly locked release tooling. Add locked Python examples and weekly dependency/security maintenance across all ecosystems.
19
+
3
20
  ## 1.12.0
4
21
 
5
22
  ### Minor Changes
package/README.md CHANGED
@@ -19,7 +19,8 @@ A CLI and microservice to fetch URLs and render them as:
19
19
 
20
20
  ## Installation
21
21
 
22
- Node.js 22.22.1 or newer is required. CI and Docker use Node.js 24. Use `npm ci`
22
+ Node.js 26.10.0 or newer is required. CI uses 26.11.1 and the latest published
23
+ Node Docker image uses 26.10.0 on Debian Trixie. Use `npm ci`
23
24
  for a locked repository install; Docker and CI share `package-lock.json` and its
24
25
  security overrides.
25
26
 
@@ -1110,11 +1110,9 @@ async function captureUrl(url, options) {
1110
1110
  await import('../src/lib.js');
1111
1111
  const { retry } = await import('../src/retry.js');
1112
1112
  const cheerio = await import('cheerio');
1113
- const nodeFetch = await import('node-fetch');
1114
1113
 
1114
+ // retry() defaults: 3 retries from a 1000ms base delay.
1115
1115
  const html = await retry(() => fetchHtml(absoluteUrl), {
1116
- retries: 3,
1117
- baseDelay: 1000,
1118
1116
  onRetry: (err, attempt) => {
1119
1117
  console.error(`Retry ${attempt} fetching page: ${err.message}`);
1120
1118
  },
@@ -1207,14 +1205,14 @@ async function captureUrl(url, options) {
1207
1205
  if (imageMap.size > 0) {
1208
1206
  for (const [imgUrl, localPath] of imageMap) {
1209
1207
  try {
1210
- const fetchFn = nodeFetch.default || nodeFetch;
1211
- const resp = await retry(() => fetchFn(imgUrl), {
1208
+ const resp = await retry(() => fetch(imgUrl), {
1212
1209
  retries: 2,
1213
1210
  baseDelay: 500,
1214
1211
  });
1215
1212
  if (resp.ok) {
1216
- const buffer = await resp.buffer();
1217
- archive.append(buffer, { name: localPath });
1213
+ archive.append(Buffer.from(await resp.arrayBuffer()), {
1214
+ name: localPath,
1215
+ });
1218
1216
  }
1219
1217
  } catch {
1220
1218
  /* skip failed image downloads */
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "@link-assistant/web-capture",
3
- "version": "1.12.0",
3
+ "version": "2.0.1",
4
4
  "description": "CLI and microservice to render web pages as HTML, Markdown, or PNG",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "web-capture": "./bin/web-capture.js"
7
+ "web-capture": "bin/web-capture.js"
8
8
  },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "CHANGELOG.md",
13
+ "README.md"
14
+ ],
9
15
  "scripts": {
10
16
  "start": "docker compose up -d",
11
- "dev": "nodemon bin/web-capture.js -- --serve",
17
+ "dev": "node --watch bin/web-capture.js --serve",
12
18
  "test": "node --experimental-vm-modules ./node_modules/.bin/jest",
13
19
  "test:watch": "node --experimental-vm-modules ./node_modules/.bin/jest --watch",
14
20
  "test:e2e:process": "node --experimental-vm-modules ./node_modules/.bin/jest tests/e2e/process.test.js",
@@ -16,16 +22,16 @@
16
22
  "test:e2e": "npm run test:e2e:process && npm run test:e2e:docker",
17
23
  "test:all": "npm run build && npm test && npm run test:e2e",
18
24
  "build": "docker compose down && docker compose build && npm start",
19
- "examples:python": "python3 examples/python/html_download.py && python3 examples/python/markdown_download.py && python3 examples/python/image_download.py",
25
+ "examples:python": "uv run --project .. --locked python examples/python/html_download.py && uv run --project .. --locked python examples/python/markdown_download.py && uv run --project .. --locked python examples/python/image_download.py",
20
26
  "examples:javascript": "node examples/js/html_download.js && node examples/js/markdown_download.js && node examples/js/image_download.js",
21
27
  "examples": "npm run build && npm run examples:javascript && npm run examples:python",
22
- "lint": "eslint .",
28
+ "lint": "node ../scripts/lint.mjs",
23
29
  "lint:fix": "eslint . --fix",
24
30
  "format": "prettier --write .",
25
31
  "format:check": "prettier --check .",
26
32
  "check:duplication": "jscpd . --baseline-from-ref origin/main --fail-on-new-clones --fail-on-empty",
27
33
  "check": "npm run lint && npm run format:check && npm run check:duplication",
28
- "prepare": "husky || true",
34
+ "prepare": "node ../scripts/install-git-hooks.mjs",
29
35
  "changeset": "changeset",
30
36
  "changeset:version": "node ../scripts/changeset-version.mjs",
31
37
  "changeset:publish": "changeset publish",
@@ -42,19 +48,16 @@
42
48
  "iconv-lite": "^0.7.3",
43
49
  "lino-arguments": "^0.3.0",
44
50
  "log-lazy": "^1.0.4",
45
- "node-fetch": "^3.3.2",
46
- "playwright": "^1.63.0",
47
- "puppeteer": "^25.12.0",
48
- "puppeteer-core": "^25.12.0",
51
+ "playwright": "^1.64.0",
52
+ "puppeteer": "^25.13.0",
53
+ "puppeteer-core": "^25.13.0",
49
54
  "turndown": "^7.2.4",
50
55
  "turndown-plugin-gfm": "^1.0.2"
51
56
  },
52
57
  "devDependencies": {
53
- "@babel/core": "^8.0.7",
54
- "@babel/preset-env": "^8.0.7",
55
58
  "@changesets/cli": "^3.0.3",
59
+ "@eslint/js": "^10.0.1",
56
60
  "@types/archiver": "^8.0.0",
57
- "babel-jest": "^30.5.2",
58
61
  "eslint": "^10.12.0",
59
62
  "eslint-config-prettier": "^10.1.8",
60
63
  "eslint-plugin-prettier": "^5.5.6",
@@ -67,8 +70,7 @@
67
70
  "nock": "^15.0.1",
68
71
  "prettier": "^3.9.9",
69
72
  "supertest": "^7.3.1",
70
- "unzipper": "^0.12.5",
71
- "@eslint/js": "^10.0.1"
73
+ "unzipper": "^0.12.5"
72
74
  },
73
75
  "type": "module",
74
76
  "keywords": [
@@ -80,14 +82,14 @@
80
82
  "license": "Unlicense",
81
83
  "repository": {
82
84
  "type": "git",
83
- "url": "https://github.com/link-assistant/web-capture"
85
+ "url": "git+https://github.com/link-assistant/web-capture.git"
84
86
  },
85
87
  "engines": {
86
- "node": ">=22.22.1"
88
+ "node": ">=26.10.0"
87
89
  },
88
90
  "lint-staged": {
89
91
  "*.{js,mjs,cjs}": [
90
- "eslint --fix --max-warnings 0",
92
+ "eslint --fix --no-warn-ignored",
91
93
  "prettier --write",
92
94
  "prettier --check"
93
95
  ],
@@ -105,6 +107,18 @@
105
107
  "overrides": {
106
108
  "@istanbuljs/load-nyc-config": {
107
109
  "js-yaml": "^4.1.1"
110
+ },
111
+ "cheerio": {
112
+ "encoding-sniffer": "^1.0.2"
113
+ },
114
+ "babel-plugin-istanbul": {
115
+ "test-exclude": "^8.0.0"
108
116
  }
117
+ },
118
+ "packageManager": "npm@12.2.0",
119
+ "allowScripts": {
120
+ "@parcel/watcher": true,
121
+ "puppeteer": true,
122
+ "unrs-resolver": true
109
123
  }
110
124
  }
package/src/archive.js CHANGED
@@ -16,7 +16,6 @@
16
16
  */
17
17
 
18
18
  import { ZipArchive } from 'archiver';
19
- import fetch from 'node-fetch';
20
19
  import * as cheerio from 'cheerio';
21
20
  import { URL } from 'url';
22
21
  import {
@@ -200,7 +199,7 @@ export async function archiveHandler(req, res) {
200
199
  baseDelay: 500,
201
200
  });
202
201
  if (imgResp.ok) {
203
- const buffer = await imgResp.buffer();
202
+ const buffer = Buffer.from(await imgResp.arrayBuffer());
204
203
  archive.append(buffer, { name: localPath });
205
204
  }
206
205
  } catch {
@@ -0,0 +1,13 @@
1
+ import { retry } from './retry.js';
2
+
3
+ // Shared image-download policy; keep HTTP errors distinct from network retries.
4
+ export async function downloadBinary(url) {
5
+ const response = await retry(() => fetch(url), {
6
+ retries: 3,
7
+ baseDelay: 1000,
8
+ });
9
+ if (!response.ok) {
10
+ throw new Error(`HTTP ${response.status}`);
11
+ }
12
+ return Buffer.from(await response.arrayBuffer());
13
+ }
package/src/docx.js CHANGED
@@ -9,18 +9,8 @@
9
9
  * engine - 'puppeteer' or 'playwright' (only used for browser rendering fallback)
10
10
  */
11
11
 
12
- import fetch from 'node-fetch';
13
12
  import * as cheerio from 'cheerio';
14
13
  import { URL } from 'url';
15
- import {
16
- Document,
17
- Packer,
18
- Paragraph,
19
- TextRun,
20
- HeadingLevel,
21
- ImageRun,
22
- ExternalHyperlink,
23
- } from 'docx';
24
14
  import { fetchHtml, convertRelativeUrls } from './lib.js';
25
15
 
26
16
  export async function docxHandler(req, res) {
@@ -35,6 +25,19 @@ export async function docxHandler(req, res) {
35
25
  const absHtml = convertRelativeUrls(html, absoluteUrl);
36
26
  const $ = cheerio.load(absHtml);
37
27
 
28
+ // Loaded on demand: docx bundles a browser util-deprecate that reads
29
+ // globalThis.localStorage on import, which makes Node >=25 print an
30
+ // ExperimentalWarning for every process that merely imports the app.
31
+ const {
32
+ Document,
33
+ Packer,
34
+ Paragraph,
35
+ TextRun,
36
+ HeadingLevel,
37
+ ImageRun,
38
+ ExternalHyperlink,
39
+ } = await import('docx');
40
+
38
41
  // Remove unwanted elements
39
42
  $('style, script, noscript, nav, footer, header').remove();
40
43
 
@@ -75,7 +78,7 @@ export async function docxHandler(req, res) {
75
78
  const imgUrl = new URL(src, absoluteUrl).href;
76
79
  const imgResp = await fetch(imgUrl);
77
80
  if (imgResp.ok) {
78
- const buffer = await imgResp.buffer();
81
+ const buffer = Buffer.from(await imgResp.arrayBuffer());
79
82
  const contentType = imgResp.headers.get('content-type') || '';
80
83
  children.push(
81
84
  new Paragraph({
package/src/fetch.js CHANGED
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { convertGoogleDriveUrl } from './lib.js';
3
2
  import { copyProxyResponseHeaders } from './proxy-headers.js';
4
3
 
@@ -14,7 +13,7 @@ export async function fetchHandler(req, res) {
14
13
  copyProxyResponseHeaders(response.headers, res);
15
14
 
16
15
  // Get the response body as buffer and send it
17
- const buffer = await response.buffer();
16
+ const buffer = Buffer.from(await response.arrayBuffer());
18
17
  res.send(buffer);
19
18
  } catch (err) {
20
19
  console.error('Fetch error:', err);
package/src/figures.js CHANGED
@@ -10,10 +10,9 @@
10
10
  * @module figures
11
11
  */
12
12
 
13
- import fetch from 'node-fetch';
14
13
  import * as cheerio from 'cheerio';
15
14
  import { URL } from 'url';
16
- import { retry } from './retry.js';
15
+ import { downloadBinary } from './binary-download.js';
17
16
  import { createBrowser, getBrowserEngine } from './browser.js';
18
17
  import { dismissPopups, scrollToLoadContent } from './popups.js';
19
18
 
@@ -86,16 +85,7 @@ export async function downloadFigures(figures, options = {}) {
86
85
  const filename = `figure-${figure.figureNum}.${ext}`;
87
86
 
88
87
  try {
89
- const resp = await retry(() => fetch(figure.src), {
90
- retries: 3,
91
- baseDelay: 1000,
92
- });
93
-
94
- if (!resp.ok) {
95
- throw new Error(`HTTP ${resp.status}`);
96
- }
97
-
98
- const buffer = await resp.buffer();
88
+ const buffer = await downloadBinary(figure.src);
99
89
 
100
90
  if (options.onProgress) {
101
91
  options.onProgress(figure.figureNum, 'downloaded');
@@ -5,7 +5,6 @@
5
5
  // the archive is self-contained. This module downloads the images and
6
6
  // rewrites the captured output accordingly.
7
7
 
8
- import fetch from 'node-fetch';
9
8
  import { URL } from 'url';
10
9
 
11
10
  const DEFAULT_USER_AGENT =
package/src/gdocs.js CHANGED
@@ -7,7 +7,6 @@
7
7
  // https://docs.googleapis.com/v1/documents/{DOCUMENT_ID}
8
8
  // Browser-model capture loads /edit and extracts DOCS_modelChunk data.
9
9
 
10
- import fetch from 'node-fetch';
11
10
  import * as he from 'he';
12
11
  import * as cheerio from 'cheerio';
13
12
  import { convertHtmlToMarkdown } from './lib.js';
package/src/github.js CHANGED
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { URL } from 'node:url';
3
2
 
4
3
  const GITHUB_API_BASE = 'https://api.github.com';
package/src/lib.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable no-useless-escape */
2
2
  // Common logic for the web-capture microservice
3
- import fetch from 'node-fetch';
4
3
  import * as cheerio from 'cheerio';
5
4
  import TurndownService from 'turndown';
6
5
  import iconv from 'iconv-lite';
@@ -151,7 +150,7 @@ export async function fetchGoogleDriveImage(url) {
151
150
  }
152
151
 
153
152
  return {
154
- buffer: await response.buffer(),
153
+ buffer: Buffer.from(await response.arrayBuffer()),
155
154
  contentType,
156
155
  downloadUrl,
157
156
  filename: `google-drive-${fileId}.${imageExtensionForContentType(contentType)}`,
@@ -13,9 +13,8 @@
13
13
  * @module localize-images
14
14
  */
15
15
 
16
- import fetch from 'node-fetch';
17
16
  import { URL } from 'url';
18
- import { retry } from './retry.js';
17
+ import { downloadBinary } from './binary-download.js';
19
18
 
20
19
  /**
21
20
  * Extract image references from markdown text.
@@ -165,16 +164,7 @@ export async function localizeImages(markdownText, options = {}) {
165
164
  }
166
165
 
167
166
  try {
168
- const resp = await retry(() => fetch(image.url), {
169
- retries: 3,
170
- baseDelay: 1000,
171
- });
172
-
173
- if (!resp.ok) {
174
- throw new Error(`HTTP ${resp.status}`);
175
- }
176
-
177
- const buffer = await resp.buffer();
167
+ const buffer = await downloadBinary(image.url);
178
168
  downloadedCount++;
179
169
 
180
170
  replacements.push({
package/src/markdown.js CHANGED
@@ -18,7 +18,6 @@ import { applyImageMode } from './extract-images.js';
18
18
  import { createBrowser, getBrowserEngine } from './browser.js';
19
19
  import { retry } from './retry.js';
20
20
  import { ZipArchive } from 'archiver';
21
- import fetch from 'node-fetch';
22
21
  import * as cheerio from 'cheerio';
23
22
 
24
23
  const INLINE_MARKDOWN_LINE_LIMIT = 1500;
@@ -8,7 +8,6 @@
8
8
  * unsupported diagnostic instead of guessed content.
9
9
  */
10
10
 
11
- import fetch from 'node-fetch';
12
11
  import * as he from 'he';
13
12
  import { URL } from 'node:url';
14
13
 
package/src/stream.js CHANGED
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { convertGoogleDriveUrl } from './lib.js';
3
2
  import { copyProxyResponseHeaders } from './proxy-headers.js';
4
3
 
package/src/transport.js CHANGED
@@ -1,5 +1,3 @@
1
- import fetch from 'node-fetch';
2
-
3
1
  export const RECEIPT_HEADERS = [
4
2
  'cache-control',
5
3
  'content-encoding',
@@ -22,22 +20,42 @@ function selectedHeaders(headers) {
22
20
  return selected;
23
21
  }
24
22
 
23
+ const TIMEOUT_CAUSE_CODES = new Set([
24
+ 'ETIMEDOUT',
25
+ 'UND_ERR_CONNECT_TIMEOUT',
26
+ 'UND_ERR_HEADERS_TIMEOUT',
27
+ 'UND_ERR_BODY_TIMEOUT',
28
+ ]);
29
+
25
30
  function classifyTransportError(error, signal) {
26
31
  if (signal?.aborted || error?.name === 'AbortError') {
27
32
  return 'cancelled';
28
33
  }
29
- if (error?.name === 'TimeoutError' || error?.type === 'request-timeout') {
34
+ if (
35
+ error?.name === 'TimeoutError' ||
36
+ TIMEOUT_CAUSE_CODES.has(error?.cause?.code)
37
+ ) {
30
38
  return 'timeout';
31
39
  }
32
- if (error instanceof TypeError) {
40
+ // Node's built-in fetch rejects every network failure with
41
+ // TypeError('fetch failed') and the real reason in `cause`. Browsers give a
42
+ // cause-less TypeError for CORS and network errors alike.
43
+ if (error?.name === 'TypeError' && !error.cause) {
33
44
  return 'cors_or_transport';
34
45
  }
35
46
  return 'transport';
36
47
  }
37
48
 
49
+ /** Message of `error` including its `cause`, e.g. "fetch failed: getaddrinfo ENOTFOUND host". */
50
+ export function describeTransportError(error) {
51
+ const message = error?.message || String(error);
52
+ const cause = error?.cause?.message;
53
+ return cause && !message.includes(cause) ? `${message}: ${cause}` : message;
54
+ }
55
+
38
56
  export class CaptureTransportError extends Error {
39
57
  constructor(error, url, signal) {
40
- super(error?.message || String(error));
58
+ super(describeTransportError(error));
41
59
  this.name = 'CaptureTransportError';
42
60
  this.cause = error;
43
61
  this.diagnostics = {
package/src/txt.js CHANGED
@@ -1,4 +1,3 @@
1
- import fetch from 'node-fetch';
2
1
  import { URL } from 'node:url';
3
2
  import {
4
3
  fetchHtml,
@@ -1,8 +0,0 @@
1
- # Changesets
2
-
3
- Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4
- with multi-package repos, or single-package repos to help you version and publish your code. You can
5
- find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6
-
7
- We have a quick list of common questions to get you started engaging with this project in
8
- [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
@@ -1,11 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
3
- "changelog": "@changesets/cli/changelog",
4
- "commit": false,
5
- "fixed": [],
6
- "linked": [],
7
- "access": "public",
8
- "baseBranch": "main",
9
- "updateInternalDependencies": "patch",
10
- "ignore": []
11
- }
package/.dockerignore DELETED
@@ -1,6 +0,0 @@
1
- node_modules
2
- reports
3
- coverage
4
- *.log
5
- .captures
6
- capture-replay.json
package/.jscpd.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "minTokens": 30,
3
- "minLines": 5,
4
- "ignore": [
5
- "**/node_modules/**",
6
- "**/build/**",
7
- "**/dist/**",
8
- "**/*.min.js",
9
- "**/coverage/**",
10
- "**/.changeset/**",
11
- "**/package-lock.json",
12
- "**/pnpm-lock.yaml",
13
- "**/yarn.lock",
14
- "**/reports/**"
15
- ],
16
- "reporters": ["console", "html"],
17
- "output": "./reports/jscpd",
18
- "mode": "weak"
19
- }
package/.lenv DELETED
@@ -1,8 +0,0 @@
1
- # web-capture default configuration
2
- # This file uses Links Notation format (key: value)
3
-
4
- # Server port
5
- PORT: 3000
6
-
7
- # Browser engine (puppeteer or playwright)
8
- BROWSER_ENGINE: puppeteer
package/.prettierignore DELETED
@@ -1,8 +0,0 @@
1
- node_modules
2
- coverage
3
- dist
4
- *.min.js
5
- package-lock.json
6
- .eslintcache
7
- CLAUDE.md
8
- tests/fixtures/deepwiki/*.md
package/.prettierrc DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "semi": true,
3
- "trailingComma": "es5",
4
- "singleQuote": true,
5
- "printWidth": 80,
6
- "tabWidth": 2,
7
- "useTabs": false,
8
- "arrowParens": "always",
9
- "endOfLine": "lf"
10
- }
package/Dockerfile DELETED
@@ -1,52 +0,0 @@
1
- FROM node:24-bookworm
2
-
3
- WORKDIR /app
4
-
5
- # Install dependencies for headless Chrome (used by both Puppeteer and Playwright)
6
- # Retry loop handles transient network errors from Debian mirrors in CI
7
- RUN apt-get update && \
8
- for i in 1 2 3; do \
9
- apt-get install -y \
10
- chromium \
11
- fonts-liberation \
12
- libasound2 \
13
- libatk-bridge2.0-0 \
14
- libatk1.0-0 \
15
- libatspi2.0-0 \
16
- libcups2 \
17
- libdbus-1-3 \
18
- libdrm2 \
19
- libgbm1 \
20
- libgtk-3-0 \
21
- libnspr4 \
22
- libnss3 \
23
- libxcomposite1 \
24
- libxdamage1 \
25
- libxfixes3 \
26
- libxrandr2 \
27
- libxshmfence1 \
28
- xdg-utils \
29
- --no-install-recommends && break || \
30
- { if [ "$i" -eq 3 ]; then exit 1; fi; \
31
- echo "apt-get install attempt $i failed, retrying..."; \
32
- apt-get update && sleep 5; }; \
33
- done && \
34
- rm -rf /var/lib/apt/lists/*
35
-
36
- # Set Puppeteer environment variables
37
- ENV PUPPETEER_SKIP_DOWNLOAD=true
38
- ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
39
- ENV PUPPETEER_ARGS="--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage"
40
-
41
- # Set Playwright environment variables
42
- ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
43
- ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
44
-
45
- COPY package.json package-lock.json ./
46
- RUN npm ci
47
-
48
- COPY . .
49
-
50
- EXPOSE 3000
51
-
52
- ENTRYPOINT ["node", "bin/web-capture.js", "--serve"]
package/babel.config.cjs DELETED
@@ -1,13 +0,0 @@
1
- module.exports = {
2
- presets: [
3
- [
4
- '@babel/preset-env',
5
- {
6
- targets: {
7
- node: 'current',
8
- },
9
- modules: false,
10
- },
11
- ],
12
- ],
13
- };
@@ -1,11 +0,0 @@
1
- services:
2
- web-capture:
3
- build:
4
- context: .
5
- dockerfile: Dockerfile
6
- ports:
7
- - '3000:3000'
8
- extra_hosts:
9
- - 'host.docker.internal:host-gateway'
10
- environment:
11
- - PORT=3000