@link-assistant/web-capture 2.0.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 (117) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/bin/web-capture.js +2 -5
  3. package/package.json +22 -11
  4. package/src/archive.js +0 -1
  5. package/src/binary-download.js +0 -1
  6. package/src/docx.js +13 -10
  7. package/src/fetch.js +0 -1
  8. package/src/gdocs-images.js +0 -1
  9. package/src/gdocs.js +0 -1
  10. package/src/github.js +0 -1
  11. package/src/lib.js +0 -1
  12. package/src/markdown.js +0 -1
  13. package/src/shared-dialog.js +0 -1
  14. package/src/stream.js +0 -1
  15. package/src/transport.js +23 -5
  16. package/src/txt.js +0 -1
  17. package/.changeset/README.md +0 -8
  18. package/.changeset/config.json +0 -11
  19. package/.dockerignore +0 -6
  20. package/.jscpd.json +0 -19
  21. package/.lenv +0 -8
  22. package/.prettierignore +0 -8
  23. package/.prettierrc +0 -10
  24. package/Dockerfile +0 -52
  25. package/docker-compose.yml +0 -11
  26. package/eslint.config.js +0 -112
  27. package/examples/js/capture_cache.js +0 -12
  28. package/examples/js/engine_comparison.js +0 -39
  29. package/examples/js/html_download.js +0 -23
  30. package/examples/js/image_download.js +0 -36
  31. package/examples/js/markdown_download.js +0 -23
  32. package/examples/js/playwright_example.js +0 -23
  33. package/examples/python/capture_client.py +0 -20
  34. package/examples/python/html_download.py +0 -3
  35. package/examples/python/image_download.py +0 -16
  36. package/examples/python/markdown_download.py +0 -3
  37. package/examples/python/playwright_example.py +0 -22
  38. package/jest.config.mjs +0 -17
  39. package/tests/ci/dependency-freshness.test.js +0 -74
  40. package/tests/ci/detect-code-changes.test.js +0 -187
  41. package/tests/ci/docker-install.test.js +0 -31
  42. package/tests/ci/rust-dockerfile.test.js +0 -54
  43. package/tests/ci/workflow-policy.test.js +0 -79
  44. package/tests/e2e/docker.test.js +0 -186
  45. package/tests/e2e/process.test.js +0 -215
  46. package/tests/fixtures/bold-coalesce-and-leakage.html +0 -27
  47. package/tests/fixtures/deepwiki/deepwiki-full-page.png +0 -0
  48. package/tests/fixtures/deepwiki/index.md +0 -1500
  49. package/tests/fixtures/deepwiki/part-1.md +0 -776
  50. package/tests/fixtures/habr-articles.js +0 -45
  51. package/tests/fixtures/heading-numbering.html +0 -19
  52. package/tests/fixtures/list-item-with-br.html +0 -8
  53. package/tests/fixtures/ol-continuous-numbering.html +0 -12
  54. package/tests/fixtures/paragraph-vs-line-break.html +0 -11
  55. package/tests/helpers/nock.js +0 -10
  56. package/tests/helpers/screenshot.js +0 -10
  57. package/tests/helpers/search-fixture.cjs +0 -17
  58. package/tests/helpers/temporary-directory.js +0 -12
  59. package/tests/integration/api-endpoints.test.js +0 -418
  60. package/tests/integration/archive-default-layout.test.js +0 -37
  61. package/tests/integration/browser-engines.test.js +0 -80
  62. package/tests/integration/browser-packaged-path.test.js +0 -45
  63. package/tests/integration/deepwiki-download.test.js +0 -144
  64. package/tests/integration/deepwiki-rendered-page.mock.test.js +0 -121
  65. package/tests/integration/formalai-contract.test.js +0 -214
  66. package/tests/integration/gdocs-public-doc.test.js +0 -315
  67. package/tests/integration/github-readme.test.js +0 -133
  68. package/tests/integration/google-drive-image.test.js +0 -246
  69. package/tests/integration/habr-article.test.js +0 -337
  70. package/tests/integration/search-endpoint.test.js +0 -88
  71. package/tests/integration/stackoverflow-download.test.js +0 -173
  72. package/tests/integration/wikipedia-download.test.js +0 -134
  73. package/tests/jest.setup.mjs +0 -7
  74. package/tests/mock/index.test.js +0 -225
  75. package/tests/unit/animation.test.js +0 -33
  76. package/tests/unit/batch.test.js +0 -114
  77. package/tests/unit/bold-coalesce-and-leakage.test.js +0 -54
  78. package/tests/unit/browser-launch-options.test.js +0 -42
  79. package/tests/unit/browser.test.js +0 -172
  80. package/tests/unit/cache.test.js +0 -182
  81. package/tests/unit/cli.test.js +0 -399
  82. package/tests/unit/convertGoogleDriveUrl.test.js +0 -89
  83. package/tests/unit/convertRelativeUrls.test.js +0 -34
  84. package/tests/unit/extract-images.test.js +0 -259
  85. package/tests/unit/fetch-html.test.js +0 -54
  86. package/tests/unit/figures.test.js +0 -74
  87. package/tests/unit/gdocs-export-headers.test.js +0 -28
  88. package/tests/unit/gdocs-preprocess.test.js +0 -36
  89. package/tests/unit/gdocs.test.js +0 -1490
  90. package/tests/unit/github.test.js +0 -156
  91. package/tests/unit/heading-image.test.js +0 -49
  92. package/tests/unit/heading-numbering.test.js +0 -28
  93. package/tests/unit/html2md-br-in-list-item.test.js +0 -23
  94. package/tests/unit/html2md-ol-numbering.test.js +0 -20
  95. package/tests/unit/html2md.test.js +0 -695
  96. package/tests/unit/image-mode-defaults.test.js +0 -54
  97. package/tests/unit/kreuzberg.test.js +0 -249
  98. package/tests/unit/latex.test.js +0 -123
  99. package/tests/unit/localize-images.test.js +0 -114
  100. package/tests/unit/markdown.test.js +0 -36
  101. package/tests/unit/metadata.test.js +0 -140
  102. package/tests/unit/package-runtime-dependencies.test.js +0 -74
  103. package/tests/unit/paragraph-vs-line-break.test.js +0 -56
  104. package/tests/unit/postprocess.test.js +0 -110
  105. package/tests/unit/proxy-headers.test.js +0 -100
  106. package/tests/unit/publish-verification.test.js +0 -54
  107. package/tests/unit/retry.test.js +0 -111
  108. package/tests/unit/search.test.js +0 -339
  109. package/tests/unit/shared-dialog.test.js +0 -173
  110. package/tests/unit/stackoverflow.test.js +0 -32
  111. package/tests/unit/stream.test.js +0 -83
  112. package/tests/unit/verify.test.js +0 -182
  113. package/tests/xpaste/xpaste-screenshot-markdown.mock.test.js +0 -375
  114. package/tests/xpaste/xpaste.e2e.test.js +0 -36
  115. package/tests/xpaste/xpaste.integration.test.js +0 -41
  116. package/tests/xpaste/xpaste.mock.test.js +0 -261
  117. package/tests/xpaste/xpaste.unit.test.js +0 -119
@@ -1,186 +0,0 @@
1
- import fetch from 'node-fetch';
2
- import http from 'http';
3
- import getPort from 'get-port';
4
- import { exec } from 'child_process';
5
- import { promisify } from 'util';
6
-
7
- const execAsync = promisify(exec);
8
- const WAIT_FOR_READY = 10000; // ms
9
- const PORT = 3000; // Use the same port as in docker-compose.yml
10
- const baseUrl = `http://localhost:${PORT}`;
11
-
12
- const timings = {};
13
- let mockServer;
14
- let mockUrl;
15
-
16
- const MOCK_HTML = `<!DOCTYPE html>
17
- <html lang="en">
18
- <head><meta charset="utf-8"><title>Example Domain</title></head>
19
- <body><h1>Example Domain</h1><p>This domain is for use in illustrative examples.</p></body>
20
- </html>`;
21
-
22
- async function isServiceRunning() {
23
- try {
24
- const res = await fetch(`${baseUrl}/health`);
25
- return res.status === 200;
26
- } catch {
27
- return false;
28
- }
29
- }
30
-
31
- beforeAll(async () => {
32
- timings.start = Date.now();
33
-
34
- // Start a local mock server to avoid depending on external network
35
- const mockPort = await getPort();
36
- mockServer = http.createServer((req, res) => {
37
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
38
- res.end(MOCK_HTML);
39
- });
40
- await new Promise((resolve) =>
41
- mockServer.listen(mockPort, '0.0.0.0', resolve)
42
- );
43
- // docker-compose.yml maps this hostname to the host gateway on Linux CI.
44
- mockUrl = `http://host.docker.internal:${mockPort}`;
45
-
46
- console.log('Checking if Docker service is already running...');
47
- const alreadyRunning = await isServiceRunning();
48
- if (!alreadyRunning) {
49
- console.log('Service not running, starting Docker container...');
50
- try {
51
- const dockerStart = Date.now();
52
- // Start the Docker container using docker compose (v2 command)
53
- const { stdout, stderr } = await execAsync('docker compose up -d');
54
- timings.dockerStartup = Date.now() - dockerStart;
55
- console.log('Docker compose output:', stdout);
56
- if (stderr) {
57
- console.error('Docker compose errors:', stderr);
58
- }
59
-
60
- // Wait for the service to be ready
61
- const readyStart = Date.now();
62
- await new Promise((resolve, reject) => {
63
- const timeout = setTimeout(() => {
64
- reject(new Error('Docker service did not start in time'));
65
- }, WAIT_FOR_READY);
66
-
67
- const checkReady = async () => {
68
- try {
69
- console.log(`Checking if service is ready at ${baseUrl}...`);
70
- const res = await fetch(`${baseUrl}/health`);
71
- if (res.status === 200) {
72
- console.log('Service is ready!');
73
- clearTimeout(timeout);
74
- resolve();
75
- } else {
76
- console.log(`Service not ready yet, status: ${res.status}`);
77
- setTimeout(checkReady, 500);
78
- }
79
- } catch (err) {
80
- console.log('Service not ready yet, error:', err.message);
81
- setTimeout(checkReady, 500);
82
- }
83
- };
84
- checkReady();
85
- });
86
- timings.serviceReady = Date.now() - readyStart;
87
- } catch (error) {
88
- console.error('Failed to start Docker container:', error);
89
- throw error;
90
- }
91
- timings.beforeAll = Date.now() - timings.start;
92
- console.log('Timing: Docker startup:', `${timings.dockerStartup}ms`);
93
- console.log('Timing: Service readiness:', `${timings.serviceReady}ms`);
94
- console.log('Timing: beforeAll total:', `${timings.beforeAll}ms`);
95
- } else {
96
- console.log('Docker service is already running. Skipping startup.');
97
- timings.beforeAll = Date.now() - timings.start;
98
- console.log(
99
- 'Timing: beforeAll (already running):',
100
- `${timings.beforeAll}ms`
101
- );
102
- }
103
- }, 30000); // Reduced timeout for beforeAll
104
-
105
- afterAll(async () => {
106
- if (mockServer) {
107
- mockServer.close();
108
- }
109
- // Stop Docker containers
110
- try {
111
- await execAsync('docker compose down');
112
- } catch {
113
- // Ignore errors during cleanup
114
- }
115
- });
116
-
117
- describe('E2E (Docker): Web Capture Microservice', () => {
118
- it('should return HTML from /html endpoint', async () => {
119
- const htmlStart = Date.now();
120
- const res = await fetch(
121
- `${baseUrl}/html?url=${encodeURIComponent(mockUrl)}`
122
- );
123
- expect(res.status).toBe(200);
124
- const text = await res.text();
125
- expect(text).toMatch(/<html/i);
126
- timings.html = Date.now() - htmlStart;
127
- console.log('Timing: /html endpoint:', `${timings.html}ms`);
128
- }, 20000);
129
-
130
- it('should return Markdown from /markdown endpoint', async () => {
131
- const mdStart = Date.now();
132
- const res = await fetch(
133
- `${baseUrl}/markdown?url=${encodeURIComponent(mockUrl)}`
134
- );
135
- expect(res.status).toBe(200);
136
- const text = await res.text();
137
- expect(text).toMatch(/example/i);
138
- timings.markdown = Date.now() - mdStart;
139
- console.log('Timing: /markdown endpoint:', `${timings.markdown}ms`);
140
- }, 20000);
141
-
142
- it('should return PNG from /image endpoint', async () => {
143
- const pngStart = Date.now();
144
- const res = await fetch(
145
- `${baseUrl}/image?url=${encodeURIComponent(mockUrl)}`
146
- );
147
- expect(res.status).toBe(200);
148
- expect(res.headers.get('content-type')).toMatch(/^image\/png/);
149
- const buf = Buffer.from(await res.arrayBuffer());
150
- // PNG signature check
151
- const pngSignature = Buffer.from([
152
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
153
- ]);
154
- expect(buf.slice(0, 8)).toEqual(pngSignature);
155
- expect(buf.length).toBeGreaterThan(100); // Should be a non-trivial PNG
156
- timings.png = Date.now() - pngStart;
157
- console.log('Timing: /image endpoint:', `${timings.png}ms`);
158
- }, 60000);
159
-
160
- it('should stream content from /stream endpoint', async () => {
161
- const startTime = Date.now();
162
- const res = await fetch(
163
- `${baseUrl}/stream?url=${encodeURIComponent(mockUrl)}`
164
- );
165
- expect(res.status).toBe(200);
166
- // Get the response as text
167
- const text = await res.text();
168
- expect(text).toMatch(/<html/i);
169
- expect(text).toMatch(/Example Domain/i);
170
- const endTime = Date.now();
171
- console.log(`Timing: /stream endpoint: ${endTime - startTime}ms`);
172
- }, 20000);
173
-
174
- it('should return content from /fetch endpoint', async () => {
175
- const startTime = Date.now();
176
- const res = await fetch(
177
- `${baseUrl}/fetch?url=${encodeURIComponent(mockUrl)}`
178
- );
179
- expect(res.status).toBe(200);
180
- const text = await res.text();
181
- expect(text).toMatch(/<html/i);
182
- expect(text).toMatch(/Example Domain/i);
183
- const endTime = Date.now();
184
- console.log(`Timing: /fetch endpoint: ${endTime - startTime}ms`);
185
- }, 20000);
186
- });
@@ -1,215 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import fetch from 'node-fetch';
3
- import getPort from 'get-port';
4
- import path from 'path';
5
-
6
- const WAIT_FOR_READY = 5000; // ms
7
- let serverProcess;
8
- let serverOutput = '';
9
- let baseUrl;
10
- let mockProcess;
11
- let mockUrl;
12
-
13
- const MOCK_HTML = `<!DOCTYPE html>
14
- <html lang="en">
15
- <head><meta charset="utf-8"><title>Example Domain</title></head>
16
- <body><h1>Example Domain</h1><p>This domain is for use in illustrative examples.</p></body>
17
- </html>`;
18
-
19
- beforeAll(async () => {
20
- const mockPort = await getPort();
21
- mockProcess = startMockServer(mockPort);
22
- await waitForProcessOutput(mockProcess, 'mock fixture server listening');
23
- mockUrl = `http://127.0.0.1:${mockPort}`;
24
-
25
- const port = await getPort();
26
- baseUrl = `http://127.0.0.1:${port}`;
27
-
28
- serverProcess = spawn(
29
- 'node',
30
- [path.resolve('bin/web-capture.js'), '--serve', '--port', port.toString()],
31
- {
32
- env: process.env,
33
- stdio: ['ignore', 'pipe', 'pipe'],
34
- }
35
- );
36
- collectProcessOutput(serverProcess, (chunk) => {
37
- serverOutput += chunk;
38
- });
39
-
40
- // Wait for the server to be ready (simple delay or poll)
41
- await new Promise((resolve, reject) => {
42
- const timeout = setTimeout(() => {
43
- reject(new Error('Server did not start in time'));
44
- }, WAIT_FOR_READY);
45
-
46
- let serverStarted = false;
47
- serverProcess.stdout.on('data', (data) => {
48
- if (
49
- data.toString().includes('listening') ||
50
- data.toString().includes('Server running')
51
- ) {
52
- serverStarted = true;
53
- clearTimeout(timeout);
54
- resolve();
55
- }
56
- });
57
-
58
- serverProcess.on('error', (error) => {
59
- clearTimeout(timeout);
60
- reject(error);
61
- });
62
-
63
- serverProcess.on('exit', (code, signal) => {
64
- if (!serverStarted) {
65
- clearTimeout(timeout);
66
- reject(
67
- new Error(`Server process exited with code ${code}, signal ${signal}`)
68
- );
69
- }
70
- });
71
-
72
- // Fallback: resolve after WAIT_FOR_READY
73
- setTimeout(resolve, WAIT_FOR_READY);
74
- });
75
- });
76
-
77
- afterAll(() => {
78
- if (serverProcess) {
79
- serverProcess.kill();
80
- }
81
- if (mockProcess) {
82
- mockProcess.kill();
83
- }
84
- });
85
-
86
- describe('E2E: Web Capture Microservice', () => {
87
- it('should return HTML from /html endpoint', async () => {
88
- const res = await fetch(
89
- `${baseUrl}/html?url=${encodeURIComponent(mockUrl)}`
90
- );
91
- const text = await expectTextResponse(res);
92
- expect(text).toMatch(/<html/i);
93
- });
94
-
95
- it('should return Markdown from /markdown endpoint', async () => {
96
- const res = await fetch(
97
- `${baseUrl}/markdown?url=${encodeURIComponent(mockUrl)}`
98
- );
99
- const text = await expectTextResponse(res);
100
- expect(text).toMatch(/example/i);
101
- });
102
-
103
- it('should return PNG from /image endpoint', async () => {
104
- const res = await fetch(
105
- `${baseUrl}/image?url=${encodeURIComponent(mockUrl)}`
106
- );
107
- await expectStatusOk(res);
108
- expect(res.headers.get('content-type')).toMatch(/^image\/png/);
109
- const buf = Buffer.from(await res.arrayBuffer());
110
- // PNG signature check
111
- const pngSignature = Buffer.from([
112
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
113
- ]);
114
- expect(buf.slice(0, 8)).toEqual(pngSignature);
115
- expect(buf.length).toBeGreaterThan(100); // Should be a non-trivial PNG
116
- }, 60000);
117
-
118
- it('should stream content from /stream endpoint', async () => {
119
- const res = await fetch(
120
- `${baseUrl}/stream?url=${encodeURIComponent(mockUrl)}`
121
- );
122
- const text = await expectTextResponse(res);
123
- expect(text).toMatch(/<html/i);
124
- expect(text).toMatch(/Example Domain/i);
125
- }, 20000);
126
-
127
- it('should return content from /fetch endpoint', async () => {
128
- const res = await fetch(
129
- `${baseUrl}/fetch?url=${encodeURIComponent(mockUrl)}`
130
- );
131
- const text = await expectTextResponse(res);
132
- expect(text).toMatch(/<html/i);
133
- expect(text).toMatch(/Example Domain/i);
134
- }, 20000);
135
- });
136
-
137
- function startMockServer(port) {
138
- const script = `
139
- const http = require('http');
140
- const html = ${JSON.stringify(MOCK_HTML)};
141
- const server = http.createServer((req, res) => {
142
- res.writeHead(200, {
143
- 'Content-Type': 'text/html; charset=utf-8',
144
- 'Content-Length': Buffer.byteLength(html),
145
- });
146
- res.end(html);
147
- });
148
- server.listen(${port}, '127.0.0.1', () => {
149
- console.log('mock fixture server listening');
150
- });
151
- process.on('SIGTERM', () => server.close(() => process.exit(0)));
152
- process.on('SIGINT', () => server.close(() => process.exit(0)));
153
- `;
154
- return spawn('node', ['-e', script], {
155
- stdio: ['ignore', 'pipe', 'pipe'],
156
- });
157
- }
158
-
159
- function collectProcessOutput(child, onChunk) {
160
- child.stdout.on('data', (data) => onChunk(data.toString()));
161
- child.stderr.on('data', (data) => onChunk(data.toString()));
162
- }
163
-
164
- async function waitForProcessOutput(child, expected) {
165
- let output = '';
166
- collectProcessOutput(child, (chunk) => {
167
- output += chunk;
168
- });
169
- await new Promise((resolve, reject) => {
170
- const timeout = setTimeout(() => {
171
- reject(
172
- new Error(`Timed out waiting for "${expected}". Output:\n${output}`)
173
- );
174
- }, WAIT_FOR_READY);
175
- child.stdout.on('data', (data) => {
176
- if (data.toString().includes(expected)) {
177
- clearTimeout(timeout);
178
- resolve();
179
- }
180
- });
181
- child.on('exit', (code, signal) => {
182
- clearTimeout(timeout);
183
- reject(new Error(`Process exited with code ${code}, signal ${signal}`));
184
- });
185
- });
186
- }
187
-
188
- async function expectTextResponse(res) {
189
- let text;
190
- try {
191
- text = await res.text();
192
- } catch (error) {
193
- throw new Error(
194
- `${error.message}\nServer output:\n${serverOutput || '(empty)'}`,
195
- { cause: error }
196
- );
197
- }
198
- expectResponseStatus(res.status, text);
199
- return text;
200
- }
201
-
202
- async function expectStatusOk(res) {
203
- if (res.status === 200) {
204
- return;
205
- }
206
- expectResponseStatus(res.status, await res.text());
207
- }
208
-
209
- function expectResponseStatus(status, body) {
210
- if (status !== 200) {
211
- throw new Error(
212
- `Expected HTTP 200, received ${status}\nBody:\n${body}\nServer output:\n${serverOutput}`
213
- );
214
- }
215
- }
@@ -1,27 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <body>
4
- <!-- Symptom A: two adjacent bold spans -->
5
- <p>
6
- <span style="font-weight: 700">13.1 </span
7
- ><span style="font-weight: 700">First subsection</span>
8
- </p>
9
-
10
- <!-- Symptom B: outer bold ends, inner bold begins, no separator -->
11
- <ol>
12
- <li><span style="font-weight: 700">Item header.</span></li>
13
- </ol>
14
- <p><span style="font-weight: 700">Note:</span> body text.</p>
15
-
16
- <!-- Symptom C: bold range spans across <br> and <img> -->
17
- <p>
18
- <span style="font-weight: 700"
19
- >Caption A:<img
20
- alt=""
21
- src="data:image/png;base64,iVBORw0KGgo=" /><br />Caption B:<img
22
- alt=""
23
- src="data:image/png;base64,iVBORw0KGgo="
24
- /></span>
25
- </p>
26
- </body>
27
- </html>