@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.
- package/CHANGELOG.md +17 -0
- package/README.md +2 -1
- package/bin/web-capture.js +5 -7
- package/package.json +32 -18
- package/src/archive.js +1 -2
- package/src/binary-download.js +13 -0
- package/src/docx.js +14 -11
- package/src/fetch.js +1 -2
- package/src/figures.js +2 -12
- package/src/gdocs-images.js +0 -1
- package/src/gdocs.js +0 -1
- package/src/github.js +0 -1
- package/src/lib.js +1 -2
- package/src/localize-images.js +2 -12
- package/src/markdown.js +0 -1
- package/src/shared-dialog.js +0 -1
- package/src/stream.js +0 -1
- package/src/transport.js +23 -5
- package/src/txt.js +0 -1
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.dockerignore +0 -6
- package/.jscpd.json +0 -19
- package/.lenv +0 -8
- package/.prettierignore +0 -8
- package/.prettierrc +0 -10
- package/Dockerfile +0 -52
- package/babel.config.cjs +0 -13
- package/docker-compose.yml +0 -11
- package/eslint.config.js +0 -112
- package/examples/js/capture_cache.js +0 -12
- package/examples/js/engine_comparison.js +0 -39
- package/examples/js/html_download.js +0 -23
- package/examples/js/image_download.js +0 -36
- package/examples/js/markdown_download.js +0 -23
- package/examples/js/playwright_example.js +0 -23
- package/examples/python/html_download.py +0 -15
- package/examples/python/image_download.py +0 -20
- package/examples/python/markdown_download.py +0 -15
- package/examples/python/playwright_example.py +0 -22
- package/jest.config.mjs +0 -20
- package/tests/ci/dependency-freshness.test.js +0 -74
- package/tests/ci/detect-code-changes.test.js +0 -174
- package/tests/ci/docker-install.test.js +0 -31
- package/tests/ci/rust-dockerfile.test.js +0 -54
- package/tests/ci/workflow-policy.test.js +0 -79
- package/tests/e2e/docker.test.js +0 -186
- package/tests/e2e/process.test.js +0 -215
- package/tests/fixtures/bold-coalesce-and-leakage.html +0 -27
- package/tests/fixtures/deepwiki/deepwiki-full-page.png +0 -0
- package/tests/fixtures/deepwiki/index.md +0 -1500
- package/tests/fixtures/deepwiki/part-1.md +0 -776
- package/tests/fixtures/habr-articles.js +0 -45
- package/tests/fixtures/heading-numbering.html +0 -19
- package/tests/fixtures/list-item-with-br.html +0 -8
- package/tests/fixtures/ol-continuous-numbering.html +0 -12
- package/tests/fixtures/paragraph-vs-line-break.html +0 -11
- package/tests/helpers/nock.js +0 -10
- package/tests/helpers/search-fixture.cjs +0 -17
- package/tests/helpers/temporary-directory.js +0 -12
- package/tests/integration/api-endpoints.test.js +0 -418
- package/tests/integration/archive-default-layout.test.js +0 -37
- package/tests/integration/browser-engines.test.js +0 -128
- package/tests/integration/browser-packaged-path.test.js +0 -45
- package/tests/integration/deepwiki-download.test.js +0 -144
- package/tests/integration/deepwiki-rendered-page.mock.test.js +0 -121
- package/tests/integration/formalai-contract.test.js +0 -214
- package/tests/integration/gdocs-public-doc.test.js +0 -315
- package/tests/integration/github-readme.test.js +0 -137
- package/tests/integration/google-drive-image.test.js +0 -246
- package/tests/integration/habr-article.test.js +0 -337
- package/tests/integration/search-endpoint.test.js +0 -88
- package/tests/integration/stackoverflow-download.test.js +0 -173
- package/tests/integration/wikipedia-download.test.js +0 -142
- package/tests/jest.setup.mjs +0 -7
- package/tests/mock/index.test.js +0 -225
- package/tests/unit/animation.test.js +0 -33
- package/tests/unit/batch.test.js +0 -114
- package/tests/unit/bold-coalesce-and-leakage.test.js +0 -54
- package/tests/unit/browser-launch-options.test.js +0 -42
- package/tests/unit/browser.test.js +0 -172
- package/tests/unit/cache.test.js +0 -182
- package/tests/unit/cli.test.js +0 -399
- package/tests/unit/convertGoogleDriveUrl.test.js +0 -89
- package/tests/unit/convertRelativeUrls.test.js +0 -34
- package/tests/unit/extract-images.test.js +0 -259
- package/tests/unit/fetch-html.test.js +0 -54
- package/tests/unit/figures.test.js +0 -74
- package/tests/unit/gdocs-export-headers.test.js +0 -28
- package/tests/unit/gdocs-preprocess.test.js +0 -36
- package/tests/unit/gdocs.test.js +0 -1490
- package/tests/unit/github.test.js +0 -156
- package/tests/unit/heading-image.test.js +0 -49
- package/tests/unit/heading-numbering.test.js +0 -28
- package/tests/unit/html2md-br-in-list-item.test.js +0 -23
- package/tests/unit/html2md-ol-numbering.test.js +0 -20
- package/tests/unit/html2md.test.js +0 -695
- package/tests/unit/image-mode-defaults.test.js +0 -54
- package/tests/unit/kreuzberg.test.js +0 -249
- package/tests/unit/latex.test.js +0 -123
- package/tests/unit/localize-images.test.js +0 -114
- package/tests/unit/markdown.test.js +0 -36
- package/tests/unit/metadata.test.js +0 -140
- package/tests/unit/package-runtime-dependencies.test.js +0 -74
- package/tests/unit/paragraph-vs-line-break.test.js +0 -56
- package/tests/unit/postprocess.test.js +0 -110
- package/tests/unit/proxy-headers.test.js +0 -100
- package/tests/unit/publish-verification.test.js +0 -57
- package/tests/unit/retry.test.js +0 -111
- package/tests/unit/search.test.js +0 -339
- package/tests/unit/shared-dialog.test.js +0 -173
- package/tests/unit/stackoverflow.test.js +0 -32
- package/tests/unit/stream.test.js +0 -85
- package/tests/unit/verify.test.js +0 -182
- package/tests/xpaste/xpaste-screenshot-markdown.mock.test.js +0 -375
- package/tests/xpaste/xpaste.e2e.test.js +0 -36
- package/tests/xpaste/xpaste.integration.test.js +0 -41
- package/tests/xpaste/xpaste.mock.test.js +0 -261
- package/tests/xpaste/xpaste.unit.test.js +0 -119
package/eslint.config.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js';
|
|
2
|
-
import prettierConfig from 'eslint-config-prettier';
|
|
3
|
-
import prettierPlugin from 'eslint-plugin-prettier';
|
|
4
|
-
|
|
5
|
-
export default [
|
|
6
|
-
js.configs.recommended,
|
|
7
|
-
prettierConfig,
|
|
8
|
-
{
|
|
9
|
-
files: ['**/*.js', '**/*.mjs'],
|
|
10
|
-
plugins: {
|
|
11
|
-
prettier: prettierPlugin,
|
|
12
|
-
},
|
|
13
|
-
languageOptions: {
|
|
14
|
-
ecmaVersion: 'latest',
|
|
15
|
-
sourceType: 'module',
|
|
16
|
-
globals: {
|
|
17
|
-
// Node.js globals
|
|
18
|
-
console: 'readonly',
|
|
19
|
-
process: 'readonly',
|
|
20
|
-
Buffer: 'readonly',
|
|
21
|
-
__dirname: 'readonly',
|
|
22
|
-
__filename: 'readonly',
|
|
23
|
-
global: 'readonly',
|
|
24
|
-
setTimeout: 'readonly',
|
|
25
|
-
clearTimeout: 'readonly',
|
|
26
|
-
setInterval: 'readonly',
|
|
27
|
-
clearInterval: 'readonly',
|
|
28
|
-
// Node.js 18+ globals
|
|
29
|
-
fetch: 'readonly',
|
|
30
|
-
// Runtime-specific globals
|
|
31
|
-
Bun: 'readonly',
|
|
32
|
-
Deno: 'readonly',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
rules: {
|
|
36
|
-
// Prettier integration
|
|
37
|
-
'prettier/prettier': 'error',
|
|
38
|
-
|
|
39
|
-
// Code quality rules
|
|
40
|
-
'no-unused-vars': 'error',
|
|
41
|
-
'no-console': 'off', // Allow console in this project
|
|
42
|
-
'no-debugger': 'error',
|
|
43
|
-
|
|
44
|
-
// Best practices
|
|
45
|
-
eqeqeq: ['error', 'always'],
|
|
46
|
-
curly: ['error', 'all'],
|
|
47
|
-
'no-var': 'error',
|
|
48
|
-
'prefer-const': 'error',
|
|
49
|
-
'prefer-arrow-callback': 'error',
|
|
50
|
-
'no-duplicate-imports': 'error',
|
|
51
|
-
|
|
52
|
-
// ES6+ features
|
|
53
|
-
'arrow-body-style': ['error', 'as-needed'],
|
|
54
|
-
'object-shorthand': ['error', 'always'],
|
|
55
|
-
'prefer-template': 'error',
|
|
56
|
-
|
|
57
|
-
// Async/await
|
|
58
|
-
'no-async-promise-executor': 'error',
|
|
59
|
-
'require-await': 'warn',
|
|
60
|
-
|
|
61
|
-
// Comments and documentation
|
|
62
|
-
'spaced-comment': ['error', 'always', { markers: ['/'] }],
|
|
63
|
-
|
|
64
|
-
// Complexity rules - reasonable thresholds for maintainability
|
|
65
|
-
complexity: ['warn', 15], // Cyclomatic complexity - allow more complex logic than strict 8
|
|
66
|
-
'max-depth': ['warn', 5], // Maximum nesting depth - slightly more lenient than strict 4
|
|
67
|
-
'max-lines-per-function': [
|
|
68
|
-
'warn',
|
|
69
|
-
{
|
|
70
|
-
max: 150, // More reasonable than strict 50 lines per function
|
|
71
|
-
skipBlankLines: true,
|
|
72
|
-
skipComments: true,
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
'max-params': ['warn', 6], // Maximum function parameters - slightly more lenient than strict 5
|
|
76
|
-
'max-statements': ['warn', 60], // Maximum statements per function - reasonable limit for orchestration functions
|
|
77
|
-
'max-lines': ['error', 1500], // Maximum lines per file - counts all lines including blank lines and comments
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
// Test files have different requirements
|
|
82
|
-
files: ['tests/**/*.js', '**/*.test.js', '**/*.test.mjs'],
|
|
83
|
-
languageOptions: {
|
|
84
|
-
globals: {
|
|
85
|
-
// Jest globals
|
|
86
|
-
describe: 'readonly',
|
|
87
|
-
it: 'readonly',
|
|
88
|
-
test: 'readonly',
|
|
89
|
-
expect: 'readonly',
|
|
90
|
-
beforeAll: 'readonly',
|
|
91
|
-
afterAll: 'readonly',
|
|
92
|
-
beforeEach: 'readonly',
|
|
93
|
-
afterEach: 'readonly',
|
|
94
|
-
jest: 'readonly',
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
rules: {
|
|
98
|
-
'require-await': 'off', // Async functions without await are common in tests
|
|
99
|
-
'max-lines-per-function': 'off', // Test files can have long test functions
|
|
100
|
-
'max-statements': 'off', // Test files can have many statements
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
ignores: [
|
|
105
|
-
'node_modules/**',
|
|
106
|
-
'coverage/**',
|
|
107
|
-
'dist/**',
|
|
108
|
-
'*.min.js',
|
|
109
|
-
'.eslintcache',
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
];
|
|
@@ -1,12 +0,0 @@
|
|
|
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));
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import fetch from 'node-fetch';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
const baseUrl = 'http://localhost:3000';
|
|
5
|
-
const targetUrl = 'https://example.com';
|
|
6
|
-
|
|
7
|
-
console.log('Comparing Puppeteer and Playwright engines...\n');
|
|
8
|
-
|
|
9
|
-
// Fetch using Puppeteer
|
|
10
|
-
console.log('1. Fetching HTML using Puppeteer (default)...');
|
|
11
|
-
const startPuppeteer = Date.now();
|
|
12
|
-
const puppeteerResponse = await fetch(
|
|
13
|
-
`${baseUrl}/html?url=${encodeURIComponent(targetUrl)}`
|
|
14
|
-
);
|
|
15
|
-
const puppeteerHtml = await puppeteerResponse.text();
|
|
16
|
-
const puppeteerTime = Date.now() - startPuppeteer;
|
|
17
|
-
fs.writeFileSync('output/puppeteer_comparison.html', puppeteerHtml);
|
|
18
|
-
console.log(` Time: ${puppeteerTime}ms`);
|
|
19
|
-
console.log(` Size: ${puppeteerHtml.length} bytes`);
|
|
20
|
-
|
|
21
|
-
// Fetch using Playwright
|
|
22
|
-
console.log('\n2. Fetching HTML using Playwright...');
|
|
23
|
-
const startPlaywright = Date.now();
|
|
24
|
-
const playwrightResponse = await fetch(
|
|
25
|
-
`${baseUrl}/html?url=${encodeURIComponent(targetUrl)}&engine=playwright`
|
|
26
|
-
);
|
|
27
|
-
const playwrightHtml = await playwrightResponse.text();
|
|
28
|
-
const playwrightTime = Date.now() - startPlaywright;
|
|
29
|
-
fs.writeFileSync('output/playwright_comparison.html', playwrightHtml);
|
|
30
|
-
console.log(` Time: ${playwrightTime}ms`);
|
|
31
|
-
console.log(` Size: ${playwrightHtml.length} bytes`);
|
|
32
|
-
|
|
33
|
-
// Summary
|
|
34
|
-
console.log('\n=== Summary ===');
|
|
35
|
-
console.log(`Puppeteer: ${puppeteerTime}ms, ${puppeteerHtml.length} bytes`);
|
|
36
|
-
console.log(`Playwright: ${playwrightTime}ms, ${playwrightHtml.length} bytes`);
|
|
37
|
-
console.log(`Difference: ${Math.abs(puppeteerTime - playwrightTime)}ms`);
|
|
38
|
-
|
|
39
|
-
console.log('\nResults saved to output/ directory.');
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const url = process.argv[2] || 'https://example.com';
|
|
7
|
-
const endpoint = `http://localhost:3000/html?url=${encodeURIComponent(url)}`;
|
|
8
|
-
|
|
9
|
-
fetch(endpoint)
|
|
10
|
-
.then((res) => {
|
|
11
|
-
if (!res.ok) {
|
|
12
|
-
throw new Error(`HTTP error! status: ${res.status}`);
|
|
13
|
-
}
|
|
14
|
-
return res.text();
|
|
15
|
-
})
|
|
16
|
-
.then((text) => {
|
|
17
|
-
const outputPath = path.join(__dirname, 'downloaded.html');
|
|
18
|
-
fs.writeFileSync(outputPath, text, 'utf-8');
|
|
19
|
-
console.log(`HTML saved to ${outputPath}`);
|
|
20
|
-
})
|
|
21
|
-
.catch((err) => {
|
|
22
|
-
console.error('Error:', err);
|
|
23
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const url = process.argv[2] || 'https://example.com';
|
|
7
|
-
const endpoint = `http://localhost:3000/image?url=${encodeURIComponent(url)}`;
|
|
8
|
-
|
|
9
|
-
fetch(endpoint)
|
|
10
|
-
.then((res) => {
|
|
11
|
-
console.log('Status:', res.status);
|
|
12
|
-
console.log('Content-Type:', res.headers.get('content-type'));
|
|
13
|
-
if (!res.ok) {
|
|
14
|
-
throw new Error(`HTTP error! status: ${res.status}`);
|
|
15
|
-
}
|
|
16
|
-
return res.arrayBuffer();
|
|
17
|
-
})
|
|
18
|
-
.then((buffer) => {
|
|
19
|
-
const buf = Buffer.from(buffer);
|
|
20
|
-
const pngSignature = Buffer.from([
|
|
21
|
-
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
|
|
22
|
-
]);
|
|
23
|
-
if (buf.slice(0, 8).equals(pngSignature)) {
|
|
24
|
-
const outputPath = path.join(__dirname, 'downloaded.png');
|
|
25
|
-
fs.writeFileSync(outputPath, buf);
|
|
26
|
-
console.log(`Image saved to ${outputPath} (valid PNG)`);
|
|
27
|
-
} else {
|
|
28
|
-
console.log(
|
|
29
|
-
'Response is not a valid PNG! First bytes:',
|
|
30
|
-
buf.slice(0, 16)
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
.catch((err) => {
|
|
35
|
-
console.error('Error:', err);
|
|
36
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const url = process.argv[2] || 'https://example.com';
|
|
7
|
-
const endpoint = `http://localhost:3000/markdown?url=${encodeURIComponent(url)}`;
|
|
8
|
-
|
|
9
|
-
fetch(endpoint)
|
|
10
|
-
.then((res) => {
|
|
11
|
-
if (!res.ok) {
|
|
12
|
-
throw new Error(`HTTP error! status: ${res.status}`);
|
|
13
|
-
}
|
|
14
|
-
return res.text();
|
|
15
|
-
})
|
|
16
|
-
.then((text) => {
|
|
17
|
-
const outputPath = path.join(__dirname, 'downloaded.md');
|
|
18
|
-
fs.writeFileSync(outputPath, text, 'utf-8');
|
|
19
|
-
console.log(`Markdown saved to ${outputPath}`);
|
|
20
|
-
})
|
|
21
|
-
.catch((err) => {
|
|
22
|
-
console.error('Error:', err);
|
|
23
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import fetch from 'node-fetch';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
const baseUrl = 'http://localhost:3000';
|
|
5
|
-
const targetUrl = 'https://example.com';
|
|
6
|
-
|
|
7
|
-
console.log('Fetching HTML using Playwright engine...');
|
|
8
|
-
const htmlResponse = await fetch(
|
|
9
|
-
`${baseUrl}/html?url=${encodeURIComponent(targetUrl)}&engine=playwright`
|
|
10
|
-
);
|
|
11
|
-
const html = await htmlResponse.text();
|
|
12
|
-
fs.writeFileSync('output/playwright_html.html', html);
|
|
13
|
-
console.log('HTML saved to output/playwright_html.html');
|
|
14
|
-
|
|
15
|
-
console.log('Fetching screenshot using Playwright engine...');
|
|
16
|
-
const imageResponse = await fetch(
|
|
17
|
-
`${baseUrl}/image?url=${encodeURIComponent(targetUrl)}&engine=playwright`
|
|
18
|
-
);
|
|
19
|
-
const imageBuffer = await imageResponse.buffer();
|
|
20
|
-
fs.writeFileSync('output/playwright_screenshot.png', imageBuffer);
|
|
21
|
-
console.log('Screenshot saved to output/playwright_screenshot.png');
|
|
22
|
-
|
|
23
|
-
console.log('Done! Both requests used Playwright engine.');
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import requests
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
url = sys.argv[1] if len(sys.argv) > 1 else 'https://example.com'
|
|
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}')
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import requests
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
url = sys.argv[1] if len(sys.argv) > 1 else 'https://example.com'
|
|
6
|
-
endpoint = f'http://localhost:3000/image?url={url}'
|
|
7
|
-
|
|
8
|
-
response = requests.get(endpoint)
|
|
9
|
-
print('Status:', response.status_code)
|
|
10
|
-
print('Content-Type:', response.headers.get('content-type'))
|
|
11
|
-
|
|
12
|
-
# PNG signature: first 8 bytes
|
|
13
|
-
png_signature = b'\x89PNG\r\n\x1a\n'
|
|
14
|
-
if response.content[:8] == png_signature:
|
|
15
|
-
output_path = os.path.join(os.path.dirname(__file__), 'downloaded.png')
|
|
16
|
-
with open(output_path, 'wb') as f:
|
|
17
|
-
f.write(response.content)
|
|
18
|
-
print(f'Image saved to {output_path} (valid PNG)')
|
|
19
|
-
else:
|
|
20
|
-
print('Response is not a valid PNG! First bytes:', response.content[:16])
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import requests
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
url = sys.argv[1] if len(sys.argv) > 1 else 'https://example.com'
|
|
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}')
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
import os
|
|
3
|
-
|
|
4
|
-
base_url = 'http://localhost:3000'
|
|
5
|
-
target_url = 'https://example.com'
|
|
6
|
-
|
|
7
|
-
# Create output directory if it doesn't exist
|
|
8
|
-
os.makedirs('output', exist_ok=True)
|
|
9
|
-
|
|
10
|
-
print('Fetching HTML using Playwright engine...')
|
|
11
|
-
html_response = requests.get(f'{base_url}/html', params={'url': target_url, 'engine': 'playwright'})
|
|
12
|
-
with open('output/playwright_html.html', 'w') as f:
|
|
13
|
-
f.write(html_response.text)
|
|
14
|
-
print('HTML saved to output/playwright_html.html')
|
|
15
|
-
|
|
16
|
-
print('Fetching screenshot using Playwright engine...')
|
|
17
|
-
image_response = requests.get(f'{base_url}/image', params={'url': target_url, 'engine': 'playwright'})
|
|
18
|
-
with open('output/playwright_screenshot.png', 'wb') as f:
|
|
19
|
-
f.write(image_response.content)
|
|
20
|
-
print('Screenshot saved to output/playwright_screenshot.png')
|
|
21
|
-
|
|
22
|
-
print('Done! Both requests used Playwright engine.')
|
package/jest.config.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
transform: {
|
|
3
|
-
'^.+\\.js$': ['babel-jest', { configFile: './babel.config.cjs' }],
|
|
4
|
-
},
|
|
5
|
-
transformIgnorePatterns: ['node_modules/(?!(turndown)/)'],
|
|
6
|
-
moduleNameMapper: {
|
|
7
|
-
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
8
|
-
},
|
|
9
|
-
testEnvironment: 'node',
|
|
10
|
-
verbose: true,
|
|
11
|
-
testMatch: [
|
|
12
|
-
'**/tests/ci/**/*.test.js',
|
|
13
|
-
'**/tests/unit/**/*.test.js',
|
|
14
|
-
'**/tests/mock/**/*.test.js',
|
|
15
|
-
'**/tests/e2e/**/*.test.js',
|
|
16
|
-
'**/tests/integration/**/*.test.js',
|
|
17
|
-
'**/tests/xpaste/**/*.test.js',
|
|
18
|
-
],
|
|
19
|
-
setupFiles: ['./tests/jest.setup.mjs'],
|
|
20
|
-
};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
import { checkVersions } from '../../../scripts/check-dependency-freshness.mjs';
|
|
3
|
-
|
|
4
|
-
const latest = async () => '2.0.0';
|
|
5
|
-
const dependencies = [{ name: 'example', current: '1.0.0' }];
|
|
6
|
-
const blockers = {
|
|
7
|
-
example: {
|
|
8
|
-
issue: 'https://github.com/example/repo/issues/1',
|
|
9
|
-
reason: 'Upstream API breaks packaged consumers',
|
|
10
|
-
},
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
test('freshness rejects old releases, missing packages and undocumented exceptions', async () => {
|
|
14
|
-
for (const current of ['1.0.0', '(missing)']) {
|
|
15
|
-
await expect(
|
|
16
|
-
checkVersions([{ name: 'example', current }], {}, latest, jest.fn())
|
|
17
|
-
).rejects.toThrow('behind 2.0.0');
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
test('freshness permits only documented open issues', async () => {
|
|
21
|
-
await expect(
|
|
22
|
-
checkVersions(dependencies, blockers, latest, async () => ({
|
|
23
|
-
state: 'open',
|
|
24
|
-
}))
|
|
25
|
-
).resolves.toBeUndefined();
|
|
26
|
-
await expect(
|
|
27
|
-
checkVersions(dependencies, blockers, latest, async () => ({
|
|
28
|
-
state: 'closed',
|
|
29
|
-
}))
|
|
30
|
-
).rejects.toThrow('is closed');
|
|
31
|
-
await expect(
|
|
32
|
-
checkVersions(
|
|
33
|
-
dependencies,
|
|
34
|
-
{ example: { ...blockers.example, reason: '' } },
|
|
35
|
-
latest,
|
|
36
|
-
jest.fn()
|
|
37
|
-
)
|
|
38
|
-
).rejects.toThrow('document an open issue');
|
|
39
|
-
await expect(
|
|
40
|
-
checkVersions(
|
|
41
|
-
dependencies,
|
|
42
|
-
{ example: { ...blockers.example, reason: ' ' } },
|
|
43
|
-
latest,
|
|
44
|
-
jest.fn()
|
|
45
|
-
)
|
|
46
|
-
).rejects.toThrow('document an open issue');
|
|
47
|
-
});
|
|
48
|
-
test('registry and issue failures fail the check rather than treating dependencies as fresh', async () => {
|
|
49
|
-
await expect(
|
|
50
|
-
checkVersions(
|
|
51
|
-
dependencies,
|
|
52
|
-
{},
|
|
53
|
-
async () => {
|
|
54
|
-
throw new Error('registry down');
|
|
55
|
-
},
|
|
56
|
-
jest.fn()
|
|
57
|
-
)
|
|
58
|
-
).rejects.toThrow('registry down');
|
|
59
|
-
await expect(
|
|
60
|
-
checkVersions(dependencies, blockers, latest, async () => {
|
|
61
|
-
throw new Error('issue unavailable');
|
|
62
|
-
})
|
|
63
|
-
).rejects.toThrow('issue unavailable');
|
|
64
|
-
});
|
|
65
|
-
test('current releases need no exception', async () => {
|
|
66
|
-
const getIssue = jest.fn();
|
|
67
|
-
await checkVersions(
|
|
68
|
-
[{ name: 'example', current: '2.0.0' }],
|
|
69
|
-
{},
|
|
70
|
-
latest,
|
|
71
|
-
getIssue
|
|
72
|
-
);
|
|
73
|
-
expect(getIssue).not.toHaveBeenCalled();
|
|
74
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'child_process';
|
|
2
|
-
import {
|
|
3
|
-
mkdirSync,
|
|
4
|
-
mkdtempSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
rmSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from 'fs';
|
|
9
|
-
import { tmpdir } from 'os';
|
|
10
|
-
import { dirname, join, resolve } from 'path';
|
|
11
|
-
import { fileURLToPath } from 'url';
|
|
12
|
-
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const __dirname = dirname(__filename);
|
|
15
|
-
const repoRoot = resolve(__dirname, '../../..');
|
|
16
|
-
const detectorPath = resolve(repoRoot, 'scripts/detect-code-changes.mjs');
|
|
17
|
-
|
|
18
|
-
function run(command, args, cwd, options = {}) {
|
|
19
|
-
return execFileSync(command, args, {
|
|
20
|
-
cwd,
|
|
21
|
-
encoding: 'utf8',
|
|
22
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
23
|
-
...options,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function git(repoPath, args) {
|
|
28
|
-
return run('git', args, repoPath);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function writeTrackedFile(repoPath, relativePath, contents) {
|
|
32
|
-
const filePath = join(repoPath, relativePath);
|
|
33
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
34
|
-
writeFileSync(filePath, contents);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function commitAll(repoPath, message) {
|
|
38
|
-
git(repoPath, ['add', '.']);
|
|
39
|
-
git(repoPath, ['commit', '-m', message]);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function createMergedFeatureRepo() {
|
|
43
|
-
const repoPath = mkdtempSync(join(tmpdir(), 'web-capture-detect-'));
|
|
44
|
-
|
|
45
|
-
initializeRepo(repoPath);
|
|
46
|
-
|
|
47
|
-
writeTrackedFile(repoPath, 'README.md', '# fixture\n');
|
|
48
|
-
commitAll(repoPath, 'initial commit');
|
|
49
|
-
|
|
50
|
-
git(repoPath, ['checkout', '-b', 'feature']);
|
|
51
|
-
writeTrackedFile(repoPath, 'rust/src/lib.rs', 'pub fn capture() {}\n');
|
|
52
|
-
commitAll(repoPath, 'add rust code');
|
|
53
|
-
|
|
54
|
-
writeTrackedFile(repoPath, 'docs/notes.md', '# docs only tail commit\n');
|
|
55
|
-
commitAll(repoPath, 'document rust change');
|
|
56
|
-
|
|
57
|
-
git(repoPath, ['checkout', 'main']);
|
|
58
|
-
git(repoPath, ['merge', '--no-ff', 'feature', '-m', 'merge feature']);
|
|
59
|
-
|
|
60
|
-
return repoPath;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function createSingleCommitRepo() {
|
|
64
|
-
return createSingleCommitRepoWithFiles({
|
|
65
|
-
'js/src/index.js': 'export const ok = true;\n',
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function createSingleCommitRepoWithFiles(files) {
|
|
70
|
-
const repoPath = mkdtempSync(join(tmpdir(), 'web-capture-detect-'));
|
|
71
|
-
|
|
72
|
-
initializeRepo(repoPath);
|
|
73
|
-
for (const [relativePath, contents] of Object.entries(files)) {
|
|
74
|
-
writeTrackedFile(repoPath, relativePath, contents);
|
|
75
|
-
}
|
|
76
|
-
commitAll(repoPath, 'initial commit');
|
|
77
|
-
|
|
78
|
-
return repoPath;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function initializeRepo(repoPath) {
|
|
82
|
-
git(repoPath, ['init']);
|
|
83
|
-
git(repoPath, ['config', 'user.email', 'ci@example.test']);
|
|
84
|
-
git(repoPath, ['config', 'user.name', 'CI Test']);
|
|
85
|
-
git(repoPath, ['checkout', '-b', 'main']);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function parseOutputs(outputFile) {
|
|
89
|
-
return Object.fromEntries(
|
|
90
|
-
readFileSync(outputFile, 'utf8')
|
|
91
|
-
.trim()
|
|
92
|
-
.split('\n')
|
|
93
|
-
.filter(Boolean)
|
|
94
|
-
.map((line) => {
|
|
95
|
-
const separatorIndex = line.indexOf('=');
|
|
96
|
-
return [line.slice(0, separatorIndex), line.slice(separatorIndex + 1)];
|
|
97
|
-
})
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function runDetector(repoPath, eventName) {
|
|
102
|
-
const outputFile = join(repoPath, `github-output-${eventName}.txt`);
|
|
103
|
-
const env = {
|
|
104
|
-
...process.env,
|
|
105
|
-
GITHUB_EVENT_NAME: eventName,
|
|
106
|
-
GITHUB_OUTPUT: outputFile,
|
|
107
|
-
};
|
|
108
|
-
delete env.GITHUB_BASE_SHA;
|
|
109
|
-
delete env.GITHUB_HEAD_SHA;
|
|
110
|
-
|
|
111
|
-
run(process.execPath, [detectorPath], repoPath, { env });
|
|
112
|
-
|
|
113
|
-
return parseOutputs(outputFile);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
describe('detect-code-changes', () => {
|
|
117
|
-
let repoPath;
|
|
118
|
-
|
|
119
|
-
afterEach(() => {
|
|
120
|
-
if (repoPath) {
|
|
121
|
-
rmSync(repoPath, { recursive: true, force: true });
|
|
122
|
-
repoPath = undefined;
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test('detects code introduced by a real merge commit pushed to main', () => {
|
|
127
|
-
repoPath = createMergedFeatureRepo();
|
|
128
|
-
|
|
129
|
-
const outputs = runDetector(repoPath, 'push');
|
|
130
|
-
|
|
131
|
-
expect(outputs['rust-code-changed']).toBe('true');
|
|
132
|
-
expect(outputs['rust-changed']).toBe('true');
|
|
133
|
-
expect(outputs['any-rust-code-changed']).toBe('true');
|
|
134
|
-
expect(outputs['any-code-changed']).toBe('true');
|
|
135
|
-
expect(outputs['docs-changed']).toBe('true');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test('keeps pull request merge commits scoped to the PR head commit', () => {
|
|
139
|
-
repoPath = createMergedFeatureRepo();
|
|
140
|
-
|
|
141
|
-
const outputs = runDetector(repoPath, 'pull_request');
|
|
142
|
-
|
|
143
|
-
expect(outputs['rust-code-changed']).toBe('false');
|
|
144
|
-
expect(outputs['rust-changed']).toBe('false');
|
|
145
|
-
expect(outputs['any-rust-code-changed']).toBe('false');
|
|
146
|
-
expect(outputs['any-code-changed']).toBe('false');
|
|
147
|
-
expect(outputs['docs-changed']).toBe('true');
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test('detects files when a push only has one commit', () => {
|
|
151
|
-
repoPath = createSingleCommitRepo();
|
|
152
|
-
|
|
153
|
-
const outputs = runDetector(repoPath, 'push');
|
|
154
|
-
|
|
155
|
-
expect(outputs['js-code-changed']).toBe('true');
|
|
156
|
-
expect(outputs['js-changed']).toBe('true');
|
|
157
|
-
expect(outputs['any-js-code-changed']).toBe('true');
|
|
158
|
-
expect(outputs['any-code-changed']).toBe('true');
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('runs JS checks for CI tests without requiring a changeset', () => {
|
|
162
|
-
repoPath = createSingleCommitRepoWithFiles({
|
|
163
|
-
'js/jest.config.mjs': 'export default {};\n',
|
|
164
|
-
'js/tests/ci/workflow-policy.test.js': 'test("ok", () => {});\n',
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const outputs = runDetector(repoPath, 'push');
|
|
168
|
-
|
|
169
|
-
expect(outputs['js-code-changed']).toBe('false');
|
|
170
|
-
expect(outputs['js-changed']).toBe('true');
|
|
171
|
-
expect(outputs['any-js-code-changed']).toBe('true');
|
|
172
|
-
expect(outputs['any-code-changed']).toBe('true');
|
|
173
|
-
});
|
|
174
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
-
|
|
4
|
-
test.each(['js', 'rust'])(
|
|
5
|
-
'%s Docker build fails when every browser package installation attempt fails',
|
|
6
|
-
(implementation) => {
|
|
7
|
-
const dockerfile = readFileSync(
|
|
8
|
-
new globalThis.URL(
|
|
9
|
-
`../../../${implementation}/Dockerfile`,
|
|
10
|
-
import.meta.url
|
|
11
|
-
),
|
|
12
|
-
'utf8'
|
|
13
|
-
);
|
|
14
|
-
const loop = dockerfile.match(/for i in 1 2 3; do[\s\S]*?done/)[0];
|
|
15
|
-
// Execute only the finite retry loop; stub installation and waiting.
|
|
16
|
-
const result = spawnSync(
|
|
17
|
-
'bash',
|
|
18
|
-
[
|
|
19
|
-
'-c',
|
|
20
|
-
`
|
|
21
|
-
apt-get() { printf '%s\\n' "$1"; [ "$1" = update ]; }
|
|
22
|
-
sleep() { return 0; }
|
|
23
|
-
${loop}
|
|
24
|
-
`,
|
|
25
|
-
],
|
|
26
|
-
{ encoding: 'utf8' }
|
|
27
|
-
);
|
|
28
|
-
expect(result.stdout.match(/^install$/gm)).toHaveLength(3);
|
|
29
|
-
expect(result.status).toBe(1);
|
|
30
|
-
}
|
|
31
|
-
);
|