@link-assistant/web-capture 1.11.2 → 2.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/.changeset/config.json +1 -1
- package/.dockerignore +6 -0
- package/.jscpd.json +4 -5
- package/CHANGELOG.md +12 -0
- package/Dockerfile +13 -11
- package/README.md +23 -4
- package/bin/web-capture.js +3 -2
- package/examples/js/capture_cache.js +12 -0
- package/examples/js/playwright_example.js +1 -1
- package/examples/python/capture_client.py +20 -0
- package/examples/python/html_download.py +2 -14
- package/examples/python/image_download.py +3 -7
- package/examples/python/markdown_download.py +2 -14
- package/examples/python/playwright_example.py +6 -6
- package/jest.config.mjs +1 -4
- package/package.json +54 -39
- package/src/archive.js +1 -1
- package/src/binary-download.js +14 -0
- package/src/cache.js +313 -0
- package/src/docx.js +1 -1
- package/src/fetch.js +1 -1
- package/src/figures.js +2 -12
- package/src/gdocs-model-wait.js +2 -4
- package/src/gdocs.js +1 -1
- package/src/index.js +7 -0
- package/src/lib.js +2 -2
- package/src/localize-images.js +2 -12
- package/src/search.js +1 -1
- package/src/shared-dialog.js +1 -1
- package/src/transport.js +2 -2
- package/tests/ci/dependency-freshness.test.js +74 -0
- package/tests/ci/detect-code-changes.test.js +36 -23
- package/tests/ci/docker-install.test.js +31 -0
- package/tests/ci/workflow-policy.test.js +18 -2
- package/tests/e2e/process.test.js +2 -1
- package/tests/helpers/nock.js +10 -0
- package/tests/helpers/screenshot.js +10 -0
- package/tests/helpers/search-fixture.cjs +1 -1
- package/tests/helpers/temporary-directory.js +12 -0
- package/tests/integration/api-endpoints.test.js +1 -1
- package/tests/integration/browser-engines.test.js +4 -52
- package/tests/integration/browser-packaged-path.test.js +45 -0
- package/tests/integration/deepwiki-rendered-page.mock.test.js +1 -1
- package/tests/integration/formalai-contract.test.js +1 -1
- package/tests/integration/github-readme.test.js +2 -6
- package/tests/integration/google-drive-image.test.js +1 -1
- package/tests/integration/search-endpoint.test.js +1 -1
- package/tests/integration/wikipedia-download.test.js +3 -11
- package/tests/mock/index.test.js +1 -1
- package/tests/unit/browser-launch-options.test.js +42 -0
- package/tests/unit/cache.test.js +182 -0
- package/tests/unit/fetch-html.test.js +1 -1
- package/tests/unit/gdocs-export-headers.test.js +1 -1
- package/tests/unit/gdocs.test.js +1 -1
- package/tests/unit/github.test.js +1 -1
- package/tests/unit/publish-verification.test.js +13 -16
- package/tests/unit/stream.test.js +8 -10
- package/tests/xpaste/xpaste-screenshot-markdown.mock.test.js +1 -1
- package/tests/xpaste/xpaste.e2e.test.js +1 -1
- package/tests/xpaste/xpaste.mock.test.js +1 -1
- package/babel.config.cjs +0 -13
package/.changeset/config.json
CHANGED
package/.dockerignore
ADDED
package/.jscpd.json
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"threshold": 0,
|
|
3
2
|
"minTokens": 30,
|
|
4
3
|
"minLines": 5,
|
|
5
|
-
"skipComments": true,
|
|
6
4
|
"ignore": [
|
|
7
5
|
"**/node_modules/**",
|
|
8
6
|
"**/build/**",
|
|
@@ -12,9 +10,10 @@
|
|
|
12
10
|
"**/.changeset/**",
|
|
13
11
|
"**/package-lock.json",
|
|
14
12
|
"**/pnpm-lock.yaml",
|
|
15
|
-
"**/yarn.lock"
|
|
13
|
+
"**/yarn.lock",
|
|
14
|
+
"**/reports/**"
|
|
16
15
|
],
|
|
17
|
-
"format": "console",
|
|
18
16
|
"reporters": ["console", "html"],
|
|
19
|
-
"output": "./reports/jscpd"
|
|
17
|
+
"output": "./reports/jscpd",
|
|
18
|
+
"mode": "weak"
|
|
20
19
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/web-capture
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
## 1.12.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- a6c74ba: Add a SHA-256 disk capture cache with TTL, offline stale replay, portable receipts, listing and pruning. Upgrade Puppeteer to 25 and forward packaged browser paths through browser-commander 0.10.0. Update other direct dependencies and require audit/freshness checks in CI. Node.js 22.22.1 or later is now required. Bundled consumers must externalize Playwright and Playwright Core.
|
|
14
|
+
|
|
3
15
|
## 1.11.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/Dockerfile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM node:
|
|
1
|
+
FROM node:26.10.0-trixie
|
|
2
2
|
|
|
3
3
|
WORKDIR /app
|
|
4
4
|
|
|
@@ -9,15 +9,15 @@ RUN apt-get update && \
|
|
|
9
9
|
apt-get install -y \
|
|
10
10
|
chromium \
|
|
11
11
|
fonts-liberation \
|
|
12
|
-
|
|
13
|
-
libatk-bridge2.0-
|
|
14
|
-
libatk1.0-
|
|
15
|
-
libatspi2.0-
|
|
16
|
-
|
|
12
|
+
libasound2t64 \
|
|
13
|
+
libatk-bridge2.0-0t64 \
|
|
14
|
+
libatk1.0-0t64 \
|
|
15
|
+
libatspi2.0-0t64 \
|
|
16
|
+
libcups2t64 \
|
|
17
17
|
libdbus-1-3 \
|
|
18
18
|
libdrm2 \
|
|
19
19
|
libgbm1 \
|
|
20
|
-
libgtk-3-
|
|
20
|
+
libgtk-3-0t64 \
|
|
21
21
|
libnspr4 \
|
|
22
22
|
libnss3 \
|
|
23
23
|
libxcomposite1 \
|
|
@@ -27,12 +27,14 @@ RUN apt-get update && \
|
|
|
27
27
|
libxshmfence1 \
|
|
28
28
|
xdg-utils \
|
|
29
29
|
--no-install-recommends && break || \
|
|
30
|
-
{
|
|
30
|
+
{ if [ "$i" -eq 3 ]; then exit 1; fi; \
|
|
31
|
+
echo "apt-get install attempt $i failed, retrying..."; \
|
|
32
|
+
apt-get update && sleep 5; }; \
|
|
31
33
|
done && \
|
|
32
34
|
rm -rf /var/lib/apt/lists/*
|
|
33
35
|
|
|
34
36
|
# Set Puppeteer environment variables
|
|
35
|
-
ENV
|
|
37
|
+
ENV PUPPETEER_SKIP_DOWNLOAD=true
|
|
36
38
|
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
|
37
39
|
ENV PUPPETEER_ARGS="--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage"
|
|
38
40
|
|
|
@@ -40,8 +42,8 @@ ENV PUPPETEER_ARGS="--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usag
|
|
|
40
42
|
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
|
41
43
|
ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
|
|
42
44
|
|
|
43
|
-
COPY package.json
|
|
44
|
-
RUN
|
|
45
|
+
COPY package.json package-lock.json ./
|
|
46
|
+
RUN npm install --global npm@12.2.0 && npm ci
|
|
45
47
|
|
|
46
48
|
COPY . .
|
|
47
49
|
|
package/README.md
CHANGED
|
@@ -19,6 +19,17 @@ A CLI and microservice to fetch URLs and render them as:
|
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
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`
|
|
24
|
+
for a locked repository install; Docker and CI share `package-lock.json` and its
|
|
25
|
+
security overrides.
|
|
26
|
+
|
|
27
|
+
Bundled applications must externalize `playwright` and `playwright-core` and
|
|
28
|
+
ship them as production packages. Playwright does not support bundling its
|
|
29
|
+
internal modules. Packaged Chromium is supported through
|
|
30
|
+
`createBrowser(engine, { executablePath: packagedPath, channel: 'chrome' })`
|
|
31
|
+
for both engines. See [the upstream bundling discussion](https://github.com/microsoft/playwright/issues/33031).
|
|
32
|
+
|
|
22
33
|
### From npm
|
|
23
34
|
|
|
24
35
|
```bash
|
|
@@ -518,14 +529,14 @@ with environment variables:
|
|
|
518
529
|
|
|
519
530
|
```bash
|
|
520
531
|
# Download the Wikipedia page (markdown + image) in every supported engine
|
|
521
|
-
WIKIPEDIA_INTEGRATION=true npm test -- --
|
|
532
|
+
WIKIPEDIA_INTEGRATION=true npm test -- --testPathPatterns="wikipedia-download"
|
|
522
533
|
|
|
523
534
|
# Download a GitHub repository page as compact txt/markdown, original HTML, and screenshots
|
|
524
|
-
GITHUB_REPOSITORY_INTEGRATION=true npm test -- --
|
|
535
|
+
GITHUB_REPOSITORY_INTEGRATION=true npm test -- --testPathPatterns="github-readme"
|
|
525
536
|
|
|
526
537
|
# Habr articles and public Google Docs live suites
|
|
527
|
-
HABR_INTEGRATION=true npm test -- --
|
|
528
|
-
GDOCS_INTEGRATION=true npm test -- --
|
|
538
|
+
HABR_INTEGRATION=true npm test -- --testPathPatterns="habr-article"
|
|
539
|
+
GDOCS_INTEGRATION=true npm test -- --testPathPatterns="gdocs-public-doc"
|
|
529
540
|
```
|
|
530
541
|
|
|
531
542
|
## Built With
|
|
@@ -598,3 +609,11 @@ JSON response does not duplicate the full provider body.
|
|
|
598
609
|
## License
|
|
599
610
|
|
|
600
611
|
[Unlicense](../LICENSE) — This is free and unencumbered software released into the public domain. You are free to copy, modify, publish, use, compile, sell, or distribute this software for any purpose, commercial or non-commercial, and by any means. See [https://unlicense.org](https://unlicense.org) for details.
|
|
612
|
+
|
|
613
|
+
## Capture cache
|
|
614
|
+
|
|
615
|
+
Both packages provide `CaptureStore` and `CachedTransport` over caller-owned transports. Captures keep the exact body bytes, final URL, status, response headers and diagnostics. Request keys include the exact URL, method and every request header in sorted order. Identical bodies share a SHA-256 file. The default TTL is 60 days; expiry is measured from fetch completion, in Unix milliseconds.
|
|
616
|
+
|
|
617
|
+
Online mode reuses fresh captures and refetches expired entries. Offline mode never invokes the transport: it returns cached captures with `cached: true` and a `stale` flag, or an `offline_cache_miss` error. Use `capture` to read these flags; the transport adapter returns the original receipt unchanged. Transport errors are not stored. Corrupt or unsupported records produce cache errors instead of returning unverified bytes.
|
|
618
|
+
|
|
619
|
+
`list` returns request metadata; `prune(now, ttl)` removes expired request records and unreferenced body files. `export(request)` and `import(json)` exchange version 1 JSON replay fixtures with byte arrays, shared between Rust and JavaScript. See [the cache format and examples](../docs/capture-cache.md).
|
package/bin/web-capture.js
CHANGED
|
@@ -1213,8 +1213,9 @@ async function captureUrl(url, options) {
|
|
|
1213
1213
|
baseDelay: 500,
|
|
1214
1214
|
});
|
|
1215
1215
|
if (resp.ok) {
|
|
1216
|
-
|
|
1217
|
-
|
|
1216
|
+
archive.append(Buffer.from(await resp.arrayBuffer()), {
|
|
1217
|
+
name: localPath,
|
|
1218
|
+
});
|
|
1218
1219
|
}
|
|
1219
1220
|
} catch {
|
|
1220
1221
|
/* skip failed image downloads */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CaptureStore, CachedTransport } from '../../src/cache.js';
|
|
2
|
+
import { fetchTransport } from '../../src/transport.js';
|
|
3
|
+
|
|
4
|
+
const store = new CaptureStore('.captures');
|
|
5
|
+
const request = { url: 'https://example.com', method: 'GET', headers: {} };
|
|
6
|
+
const online = new CachedTransport(fetchTransport, store);
|
|
7
|
+
const first = await online.capture(request);
|
|
8
|
+
const offline = new CachedTransport(fetchTransport, store, { offline: true });
|
|
9
|
+
const replay = await offline.capture(request);
|
|
10
|
+
console.log(replay.sha256, replay.cached, replay.stale);
|
|
11
|
+
console.log('Exact replay:', first.receipt.body.equals(replay.receipt.body));
|
|
12
|
+
console.log('Portable fixture:', await store.export(request));
|
|
@@ -16,7 +16,7 @@ console.log('Fetching screenshot using Playwright engine...');
|
|
|
16
16
|
const imageResponse = await fetch(
|
|
17
17
|
`${baseUrl}/image?url=${encodeURIComponent(targetUrl)}&engine=playwright`
|
|
18
18
|
);
|
|
19
|
-
const imageBuffer = await imageResponse.
|
|
19
|
+
const imageBuffer = Buffer.from(await imageResponse.arrayBuffer());
|
|
20
20
|
fs.writeFileSync('output/playwright_screenshot.png', imageBuffer);
|
|
21
21
|
console.log('Screenshot saved to output/playwright_screenshot.png');
|
|
22
22
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Shared request policy for the capture-service examples."""
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sys
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def capture(kind, url=None, **options):
|
|
9
|
+
if url is None:
|
|
10
|
+
url = sys.argv[1] if len(sys.argv) > 1 else 'https://example.com'
|
|
11
|
+
endpoint = os.environ.get('WEB_CAPTURE_URL', 'http://localhost:3000').rstrip('/')
|
|
12
|
+
response = requests.get(f'{endpoint}/{kind}', params={'url': url, **options}, timeout=30)
|
|
13
|
+
response.raise_for_status()
|
|
14
|
+
return response
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def download_text(kind, filename):
|
|
18
|
+
output = Path(__file__).with_name(filename)
|
|
19
|
+
output.write_text(capture(kind).text, encoding='utf-8')
|
|
20
|
+
print(f'{kind.title()} saved to {output}')
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
import requests
|
|
3
|
-
import os
|
|
1
|
+
from capture_client import download_text
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
endpoint = f'http://localhost:3000/html?url={url}'
|
|
7
|
-
|
|
8
|
-
response = requests.get(endpoint)
|
|
9
|
-
response.raise_for_status()
|
|
10
|
-
|
|
11
|
-
output_path = os.path.join(os.path.dirname(__file__), 'downloaded.html')
|
|
12
|
-
with open(output_path, 'w', encoding='utf-8') as f:
|
|
13
|
-
f.write(response.text)
|
|
14
|
-
|
|
15
|
-
print(f'HTML saved to {output_path}')
|
|
3
|
+
download_text('html', 'downloaded.html')
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import requests
|
|
1
|
+
from capture_client import capture
|
|
3
2
|
import os
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
endpoint = f'http://localhost:3000/image?url={url}'
|
|
7
|
-
|
|
8
|
-
response = requests.get(endpoint)
|
|
4
|
+
response = capture('image')
|
|
9
5
|
print('Status:', response.status_code)
|
|
10
6
|
print('Content-Type:', response.headers.get('content-type'))
|
|
11
7
|
|
|
@@ -17,4 +13,4 @@ if response.content[:8] == png_signature:
|
|
|
17
13
|
f.write(response.content)
|
|
18
14
|
print(f'Image saved to {output_path} (valid PNG)')
|
|
19
15
|
else:
|
|
20
|
-
print('Response is not a valid PNG! First bytes:', response.content[:16])
|
|
16
|
+
print('Response is not a valid PNG! First bytes:', response.content[:16])
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
import requests
|
|
3
|
-
import os
|
|
1
|
+
from capture_client import download_text
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
endpoint = f'http://localhost:3000/markdown?url={url}'
|
|
7
|
-
|
|
8
|
-
response = requests.get(endpoint)
|
|
9
|
-
response.raise_for_status()
|
|
10
|
-
|
|
11
|
-
output_path = os.path.join(os.path.dirname(__file__), 'downloaded.md')
|
|
12
|
-
with open(output_path, 'w', encoding='utf-8') as f:
|
|
13
|
-
f.write(response.text)
|
|
14
|
-
|
|
15
|
-
print(f'Markdown saved to {output_path}')
|
|
3
|
+
download_text('markdown', 'downloaded.md')
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import sys
|
|
2
|
+
from capture_client import capture
|
|
2
3
|
import os
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
target_url = 'https://example.com'
|
|
5
|
+
target_url = sys.argv[1] if len(sys.argv) > 1 else 'https://example.com'
|
|
6
6
|
|
|
7
7
|
# Create output directory if it doesn't exist
|
|
8
8
|
os.makedirs('output', exist_ok=True)
|
|
9
9
|
|
|
10
10
|
print('Fetching HTML using Playwright engine...')
|
|
11
|
-
html_response =
|
|
12
|
-
with open('output/playwright_html.html', 'w') as f:
|
|
11
|
+
html_response = capture('html', target_url, engine='playwright')
|
|
12
|
+
with open('output/playwright_html.html', 'w', encoding='utf-8') as f:
|
|
13
13
|
f.write(html_response.text)
|
|
14
14
|
print('HTML saved to output/playwright_html.html')
|
|
15
15
|
|
|
16
16
|
print('Fetching screenshot using Playwright engine...')
|
|
17
|
-
image_response =
|
|
17
|
+
image_response = capture('image', target_url, engine='playwright')
|
|
18
18
|
with open('output/playwright_screenshot.png', 'wb') as f:
|
|
19
19
|
f.write(image_response.content)
|
|
20
20
|
print('Screenshot saved to output/playwright_screenshot.png')
|
package/jest.config.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/web-capture",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CLI and microservice to render web pages as HTML, Markdown, or PNG",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,22 +8,22 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "docker compose up -d",
|
|
11
|
-
"dev": "
|
|
11
|
+
"dev": "node --watch bin/web-capture.js --serve",
|
|
12
12
|
"test": "node --experimental-vm-modules ./node_modules/.bin/jest",
|
|
13
13
|
"test:watch": "node --experimental-vm-modules ./node_modules/.bin/jest --watch",
|
|
14
|
-
"test:e2e:process": "node --experimental-vm-modules ./node_modules/.bin/jest tests/e2e
|
|
15
|
-
"test:e2e:docker": "node --experimental-vm-modules ./node_modules/.bin/jest tests/e2e
|
|
16
|
-
"test:e2e": "
|
|
17
|
-
"test:all": "
|
|
18
|
-
"build": "docker compose down && docker compose build &&
|
|
19
|
-
"examples:python": "
|
|
14
|
+
"test:e2e:process": "node --experimental-vm-modules ./node_modules/.bin/jest tests/e2e/process.test.js",
|
|
15
|
+
"test:e2e:docker": "node --experimental-vm-modules ./node_modules/.bin/jest tests/e2e/docker.test.js",
|
|
16
|
+
"test:e2e": "npm run test:e2e:process && npm run test:e2e:docker",
|
|
17
|
+
"test:all": "npm run build && npm test && npm run test:e2e",
|
|
18
|
+
"build": "docker compose down && docker compose build && npm start",
|
|
19
|
+
"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
20
|
"examples:javascript": "node examples/js/html_download.js && node examples/js/markdown_download.js && node examples/js/image_download.js",
|
|
21
|
-
"examples": "
|
|
21
|
+
"examples": "npm run build && npm run examples:javascript && npm run examples:python",
|
|
22
22
|
"lint": "eslint .",
|
|
23
23
|
"lint:fix": "eslint . --fix",
|
|
24
24
|
"format": "prettier --write .",
|
|
25
25
|
"format:check": "prettier --check .",
|
|
26
|
-
"check:duplication": "jscpd .",
|
|
26
|
+
"check:duplication": "jscpd . --baseline-from-ref origin/main --fail-on-new-clones --fail-on-empty",
|
|
27
27
|
"check": "npm run lint && npm run format:check && npm run check:duplication",
|
|
28
28
|
"prepare": "husky || true",
|
|
29
29
|
"changeset": "changeset",
|
|
@@ -32,42 +32,40 @@
|
|
|
32
32
|
"changeset:status": "changeset status --since=origin/main"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@kreuzberg/html-to-markdown-node": "^3.
|
|
35
|
+
"@kreuzberg/html-to-markdown-node": "^3.7.2",
|
|
36
36
|
"archiver": "^8.0.0",
|
|
37
|
-
"browser-commander": "
|
|
38
|
-
"cheerio": "^1.
|
|
39
|
-
"docx": "^9.
|
|
40
|
-
"express": "^
|
|
41
|
-
"he": "^
|
|
42
|
-
"iconv-lite": "^0.
|
|
37
|
+
"browser-commander": "0.10.0",
|
|
38
|
+
"cheerio": "^1.2.0",
|
|
39
|
+
"docx": "^9.9.0",
|
|
40
|
+
"express": "^5.2.1",
|
|
41
|
+
"he": "^2.0.0",
|
|
42
|
+
"iconv-lite": "^0.7.3",
|
|
43
43
|
"lino-arguments": "^0.3.0",
|
|
44
44
|
"log-lazy": "^1.0.4",
|
|
45
|
-
"node-fetch": "^
|
|
46
|
-
"playwright": "^1.
|
|
47
|
-
"puppeteer": "^
|
|
48
|
-
"puppeteer-core": "^
|
|
49
|
-
"turndown": "^7.
|
|
45
|
+
"node-fetch": "^3.3.2",
|
|
46
|
+
"playwright": "^1.64.0",
|
|
47
|
+
"puppeteer": "^25.12.0",
|
|
48
|
+
"puppeteer-core": "^25.12.0",
|
|
49
|
+
"turndown": "^7.2.4",
|
|
50
50
|
"turndown-plugin-gfm": "^1.0.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@
|
|
54
|
-
"@
|
|
55
|
-
"
|
|
56
|
-
"@types/archiver": "^7.0.0",
|
|
57
|
-
"babel-jest": "^29.7.0",
|
|
58
|
-
"eslint": "^9.38.0",
|
|
53
|
+
"@changesets/cli": "^3.0.3",
|
|
54
|
+
"@types/archiver": "^8.0.0",
|
|
55
|
+
"eslint": "^10.12.0",
|
|
59
56
|
"eslint-config-prettier": "^10.1.8",
|
|
60
|
-
"eslint-plugin-prettier": "^5.5.
|
|
61
|
-
"get-port": "^7.
|
|
57
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
58
|
+
"get-port": "^7.2.0",
|
|
62
59
|
"husky": "^9.1.7",
|
|
63
|
-
"jest": "^
|
|
64
|
-
"jscpd": "^4.0
|
|
65
|
-
"lint-staged": "^
|
|
66
|
-
"markdown-it": "^
|
|
67
|
-
"nock": "^
|
|
68
|
-
"prettier": "^3.
|
|
69
|
-
"supertest": "^
|
|
70
|
-
"unzipper": "^0.12.
|
|
60
|
+
"jest": "^30.5.2",
|
|
61
|
+
"jscpd": "^5.4.0",
|
|
62
|
+
"lint-staged": "^17.6.0",
|
|
63
|
+
"markdown-it": "^15.0.2",
|
|
64
|
+
"nock": "^15.0.1",
|
|
65
|
+
"prettier": "^3.9.9",
|
|
66
|
+
"supertest": "^7.3.1",
|
|
67
|
+
"unzipper": "^0.12.5",
|
|
68
|
+
"@eslint/js": "^10.0.1"
|
|
71
69
|
},
|
|
72
70
|
"type": "module",
|
|
73
71
|
"keywords": [
|
|
@@ -82,7 +80,7 @@
|
|
|
82
80
|
"url": "https://github.com/link-assistant/web-capture"
|
|
83
81
|
},
|
|
84
82
|
"engines": {
|
|
85
|
-
"node": ">=
|
|
83
|
+
"node": ">=26.10.0"
|
|
86
84
|
},
|
|
87
85
|
"lint-staged": {
|
|
88
86
|
"*.{js,mjs,cjs}": [
|
|
@@ -94,5 +92,22 @@
|
|
|
94
92
|
"prettier --write",
|
|
95
93
|
"prettier --check"
|
|
96
94
|
]
|
|
95
|
+
},
|
|
96
|
+
"dependencyBlockers": {
|
|
97
|
+
"browser-commander": {
|
|
98
|
+
"issue": "https://github.com/link-assistant/web-capture/issues/160",
|
|
99
|
+
"reason": "Keep the first compatible packaged-browser release; later versions add native better-sqlite3 dependencies."
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"overrides": {
|
|
103
|
+
"@istanbuljs/load-nyc-config": {
|
|
104
|
+
"js-yaml": "^4.1.1"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"packageManager": "npm@12.2.0",
|
|
108
|
+
"allowScripts": {
|
|
109
|
+
"@parcel/watcher": true,
|
|
110
|
+
"puppeteer": true,
|
|
111
|
+
"unrs-resolver": true
|
|
97
112
|
}
|
|
98
113
|
}
|
package/src/archive.js
CHANGED
|
@@ -200,7 +200,7 @@ export async function archiveHandler(req, res) {
|
|
|
200
200
|
baseDelay: 500,
|
|
201
201
|
});
|
|
202
202
|
if (imgResp.ok) {
|
|
203
|
-
const buffer = await imgResp.
|
|
203
|
+
const buffer = Buffer.from(await imgResp.arrayBuffer());
|
|
204
204
|
archive.append(buffer, { name: localPath });
|
|
205
205
|
}
|
|
206
206
|
} catch {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { retry } from './retry.js';
|
|
3
|
+
|
|
4
|
+
// Shared image-download policy; keep HTTP errors distinct from network retries.
|
|
5
|
+
export async function downloadBinary(url) {
|
|
6
|
+
const response = await retry(() => fetch(url), {
|
|
7
|
+
retries: 3,
|
|
8
|
+
baseDelay: 1000,
|
|
9
|
+
});
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
throw new Error(`HTTP ${response.status}`);
|
|
12
|
+
}
|
|
13
|
+
return Buffer.from(await response.arrayBuffer());
|
|
14
|
+
}
|