single-file-cli 2.3.0 → 2.3.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/build.sh +1 -1
- package/deno.json +1 -1
- package/lib/cdp-client.js +123 -41
- package/lib/single-file-archive.js +4 -4
- package/lib/single-file-bundle.js +1 -1
- package/lib/version.js +1 -1
- package/options.js +1 -1
- package/package.json +1 -1
- package/single-file-cli-api.js +6 -0
- package/test/e2e/oopif.test.js +86 -0
package/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "2.3.
|
|
1
|
+
export const version = "2.3.1";
|
package/options.js
CHANGED
|
@@ -336,7 +336,7 @@ function printUsage() {
|
|
|
336
336
|
optionType = optionType.replace("[]", "*");
|
|
337
337
|
}
|
|
338
338
|
const optionDescription = optionInfo.description;
|
|
339
|
-
const optionDefaultValue = optionInfo.defaultValue === undefined ? "" : `(default: ${JSON.stringify(optionInfo.defaultValue)})`;
|
|
339
|
+
const optionDefaultValue = optionInfo.defaultValue === undefined ? "" : `(default: ${JSON.stringify(optionInfo.defaultValue).replace(/[\u007f-\u009f]/g, character => "\\u" + character.charCodeAt(0).toString(16).padStart(4, "0"))})`;
|
|
340
340
|
console.log(` --${optionName}: ${optionDescription} <${optionType}> ${optionDefaultValue}`); // eslint-disable-line no-console
|
|
341
341
|
});
|
|
342
342
|
console.log(""); // eslint-disable-line no-console
|
package/package.json
CHANGED
package/single-file-cli-api.js
CHANGED
|
@@ -250,6 +250,12 @@ async function runNextTask() {
|
|
|
250
250
|
task.promise = capturePage(taskOptions);
|
|
251
251
|
const pageData = await task.promise;
|
|
252
252
|
task.status = STATE_PROCESSED;
|
|
253
|
+
if (options.crawlLinks || tasks.length > 1) {
|
|
254
|
+
const processedCount = tasks.filter(task => task.status == STATE_PROCESSED).length;
|
|
255
|
+
const filenameInfo = pageData && pageData.filename && !options.crawlSaveArchive ? " (" + pageData.filename + ")" : "";
|
|
256
|
+
// written to stderr so that stdout stays parseable when using --dump-content
|
|
257
|
+
console.error(`[${processedCount}/${tasks.length}] ${pageData ? "saved" : "failed"} ${task.url}${filenameInfo}`); // eslint-disable-line no-console
|
|
258
|
+
}
|
|
253
259
|
if (pageData) {
|
|
254
260
|
task.filename = pageData.filename;
|
|
255
261
|
task.title = pageData.title;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* global URL */
|
|
2
|
+
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { createServer } from "node:http";
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import process from "node:process";
|
|
13
|
+
|
|
14
|
+
const execFileAsync = promisify(execFile);
|
|
15
|
+
const cliDirectory = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
16
|
+
const FRAME_MARKER = "OUT_OF_PROCESS_FRAME_CONTENT";
|
|
17
|
+
|
|
18
|
+
test("blocked URL patterns and extra HTTP headers apply in cross-origin frames", { timeout: 120000 }, async () => {
|
|
19
|
+
const BLOCKED_STYLE = "rgb(123,45,67)";
|
|
20
|
+
const GATED_STYLE = "rgb(89,89,89)";
|
|
21
|
+
const frameServer = createServer((request, response) => {
|
|
22
|
+
const { pathname } = new URL(request.url, "http://127.0.0.1");
|
|
23
|
+
if (pathname === "/tracker.css") {
|
|
24
|
+
response.writeHead(200, { "content-type": "text/css" }).end("h2 { color: " + BLOCKED_STYLE + "; }");
|
|
25
|
+
} else if (pathname === "/gated.css") {
|
|
26
|
+
if (request.headers["x-test-header"] === "yes") {
|
|
27
|
+
response.writeHead(200, { "content-type": "text/css" }).end("h3 { color: " + GATED_STYLE + "; }");
|
|
28
|
+
} else {
|
|
29
|
+
response.writeHead(404).end();
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
response.writeHead(200, { "content-type": "text/html" }).end(
|
|
33
|
+
"<html><head><link rel=\"stylesheet\" href=\"/tracker.css\"><link rel=\"stylesheet\" href=\"/gated.css\"></head>" +
|
|
34
|
+
"<body><h2>blocked</h2><h3>gated</h3></body></html>");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
await new Promise(resolve => frameServer.listen(0, "127.0.0.1", resolve));
|
|
38
|
+
const frameUrl = "http://127.0.0.1:" + frameServer.address().port + "/frame.html";
|
|
39
|
+
const server = createServer((request, response) => {
|
|
40
|
+
response.writeHead(200, { "content-type": "text/html" }).end("<html><body><iframe src=\"" + frameUrl + "\"></iframe></body></html>");
|
|
41
|
+
});
|
|
42
|
+
await new Promise(resolve => server.listen(0, "localhost", resolve));
|
|
43
|
+
const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
|
|
44
|
+
try {
|
|
45
|
+
const outputPath = join(directory, "out.html");
|
|
46
|
+
const url = "http://localhost:" + server.address().port + "/top.html";
|
|
47
|
+
await execFileAsync(process.execPath, [
|
|
48
|
+
"single-file-node.js", url, outputPath,
|
|
49
|
+
"--blocked-URL-pattern", "tracker",
|
|
50
|
+
"--http-header", "x-test-header=yes"
|
|
51
|
+
], { cwd: cliDirectory });
|
|
52
|
+
const content = await readFile(outputPath, "utf8");
|
|
53
|
+
assert.ok(!content.includes(BLOCKED_STYLE), "a blocked stylesheet leaked into the frame");
|
|
54
|
+
assert.ok(content.includes(GATED_STYLE), "the extra HTTP header was not sent for a frame resource");
|
|
55
|
+
} finally {
|
|
56
|
+
await rm(directory, { recursive: true });
|
|
57
|
+
server.close();
|
|
58
|
+
frameServer.close();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("cross-origin frames are captured in the saved page", { timeout: 120000 }, async () => {
|
|
63
|
+
// localhost and 127.0.0.1 are different sites, so site isolation renders
|
|
64
|
+
// the frame out of process, in a separate CDP target
|
|
65
|
+
const frameServer = createServer((request, response) => {
|
|
66
|
+
response.writeHead(200, { "content-type": "text/html" }).end("<html><body><p>" + FRAME_MARKER + "</p></body></html>");
|
|
67
|
+
});
|
|
68
|
+
await new Promise(resolve => frameServer.listen(0, "127.0.0.1", resolve));
|
|
69
|
+
const frameUrl = "http://127.0.0.1:" + frameServer.address().port + "/frame.html";
|
|
70
|
+
const server = createServer((request, response) => {
|
|
71
|
+
response.writeHead(200, { "content-type": "text/html" }).end("<html><body><iframe src=\"" + frameUrl + "\"></iframe></body></html>");
|
|
72
|
+
});
|
|
73
|
+
await new Promise(resolve => server.listen(0, "localhost", resolve));
|
|
74
|
+
const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
|
|
75
|
+
try {
|
|
76
|
+
const outputPath = join(directory, "out.html");
|
|
77
|
+
const url = "http://localhost:" + server.address().port + "/top.html";
|
|
78
|
+
await execFileAsync(process.execPath, ["single-file-node.js", url, outputPath], { cwd: cliDirectory });
|
|
79
|
+
const content = await readFile(outputPath, "utf8");
|
|
80
|
+
assert.ok(content.includes(FRAME_MARKER), "the out-of-process frame was saved empty");
|
|
81
|
+
} finally {
|
|
82
|
+
await rm(directory, { recursive: true });
|
|
83
|
+
server.close();
|
|
84
|
+
frameServer.close();
|
|
85
|
+
}
|
|
86
|
+
});
|