single-file-cli 2.1.0 → 2.1.2

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.
@@ -18,8 +18,8 @@ const TEST_TIMEOUT = 120000;
18
18
  let crawlPromise;
19
19
 
20
20
  test("a page linked with and without fragment is captured once", { timeout: TEST_TIMEOUT }, async () => {
21
- const { filenames } = await getCrawlResult();
22
- assert.ok(filenames.includes("Top Page.html"));
21
+ const { filenames, stderr } = await getCrawlResult();
22
+ assert.ok(filenames.includes("Top Page.html"), "missing top page, captured: " + filenames.join(", ") + ", stderr: " + stderr);
23
23
  assert.equal(filenames.filter(filename => filename.startsWith("Linked Page")).length, 1);
24
24
  });
25
25
 
@@ -66,7 +66,9 @@ test("mail and script links are not crawled as external links", { timeout: TEST_
66
66
  "--errors-file", join(directory, "errors.txt"),
67
67
  "--max-parallel-workers", "1"
68
68
  ], { cwd: cliDirectory });
69
- assert.deepEqual(await readdir(directory), ["Mail Top.html"]);
69
+ const filenames = await readdir(directory);
70
+ const errors = filenames.includes("errors.txt") ? await readFile(join(directory, "errors.txt"), "utf8") : "";
71
+ assert.deepEqual(filenames, ["Mail Top.html"], "errors: " + errors);
70
72
  } finally {
71
73
  await rm(directory, { recursive: true });
72
74
  server.close();
@@ -115,7 +117,7 @@ async function runCrawl() {
115
117
  const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
116
118
  try {
117
119
  const url = "http://localhost:" + server.address().port + "/";
118
- await execFileAsync(process.execPath, [
120
+ const { stderr } = await execFileAsync(process.execPath, [
119
121
  "single-file-node.js", url,
120
122
  "--crawl-links",
121
123
  "--crawl-replace-URLs",
@@ -128,7 +130,7 @@ async function runCrawl() {
128
130
  for (const filename of filenames) {
129
131
  pages[filename] = await readFile(join(directory, filename), "utf8");
130
132
  }
131
- return { filenames, pages, otherServerRequests };
133
+ return { filenames, pages, otherServerRequests, stderr };
132
134
  } finally {
133
135
  await rm(directory, { recursive: true });
134
136
  server.close();
@@ -0,0 +1,31 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
6
+ import { tmpdir } from "node:os";
7
+ import { join, dirname } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import process from "node:process";
10
+
11
+ const execFileAsync = promisify(execFile);
12
+ const cliDirectory = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
13
+
14
+ test("errors file lines include the error message", { timeout: 120000 }, async () => {
15
+ const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
16
+ try {
17
+ const errorsPath = join(directory, "errors.txt");
18
+ const url = "http://localhost:1/";
19
+ await execFileAsync(process.execPath, [
20
+ "single-file-node.js", url, join(directory, "out.html"),
21
+ "--errors-file", errorsPath
22
+ ], { cwd: cliDirectory });
23
+ const content = await readFile(errorsPath, "utf8");
24
+ assert.ok(content.includes("URL: " + url));
25
+ const errorMessage = content.match(/Error: (.*)/);
26
+ assert.ok(errorMessage);
27
+ assert.ok(errorMessage[1].trim().length > 0);
28
+ } finally {
29
+ await rm(directory, { recursive: true });
30
+ }
31
+ });
@@ -9,13 +9,14 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
9
9
  import { tmpdir } from "node:os";
10
10
  import { join, dirname } from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
+ import { Buffer } from "node:buffer";
12
13
  import process from "node:process";
13
14
 
14
15
  const execFileAsync = promisify(execFile);
15
16
  const cliDirectory = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
16
17
  const LOAD_MARKER = "MARKER_ADDED_AFTER_LOAD_EVENT";
17
18
  const SLOW_RESOURCE_DELAY = 3500;
18
- const PIXEL_PNG = Uint8Array.fromBase64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==");
19
+ const PIXEL_PNG = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64");
19
20
 
20
21
  const TOP_PAGE = `<html><head><title>top</title></head><body>
21
22
  <img src="/slow.png">
@@ -29,7 +30,7 @@ addEventListener("load", () => {
29
30
  </script>
30
31
  </body></html>`;
31
32
 
32
- test("capture waits for the top frame, not a fast iframe", { timeout: 60000 }, async () => {
33
+ test("capture waits for the top frame, not a fast iframe", { timeout: 120000 }, async () => {
33
34
  const server = createServer((request, response) => {
34
35
  const { pathname } = new URL(request.url, "http://localhost");
35
36
  if (pathname === "/top.html") {
@@ -50,7 +51,8 @@ test("capture waits for the top frame, not a fast iframe", { timeout: 60000 }, a
50
51
  await execFileAsync(process.execPath, [
51
52
  "single-file-node.js", url, outputPath,
52
53
  "--browser-wait-until", "networkIdle",
53
- "--browser-wait-until-delay", "1000"
54
+ "--browser-wait-until-delay", "1000",
55
+ "--browser-wait-until-fallback", "false"
54
56
  ], { cwd: cliDirectory });
55
57
  const content = await readFile(outputPath, "utf8");
56
58
  assert.ok(content.includes(LOAD_MARKER), "page was captured before the top frame finished loading");
@@ -1,5 +1,3 @@
1
- /* global TextDecoder */
2
-
3
1
  import { test } from "node:test";
4
2
  import assert from "node:assert/strict";
5
3
  import { createServer } from "node:http";
@@ -9,13 +7,14 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
9
7
  import { tmpdir } from "node:os";
10
8
  import { join, dirname } from "node:path";
11
9
  import { fileURLToPath } from "node:url";
10
+ import { Buffer } from "node:buffer";
12
11
  import process from "node:process";
13
12
 
14
13
  const execFileAsync = promisify(execFile);
15
14
  const cliDirectory = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
16
15
  const ZIP_SIGNATURE = "PK\u0003\u0004";
17
16
 
18
- test("output-json embeds compressed content as base64", { timeout: 60000 }, async () => {
17
+ test("output-json embeds compressed content as base64", { timeout: 120000 }, async () => {
19
18
  const server = createServer((_, response) => response
20
19
  .writeHead(200, { "content-type": "text/html" })
21
20
  .end("<html><head><title>JSON Output</title></head><body>content</body></html>"));
@@ -24,14 +23,20 @@ test("output-json embeds compressed content as base64", { timeout: 60000 }, asyn
24
23
  try {
25
24
  const outputPath = join(directory, "page.json");
26
25
  const url = "http://localhost:" + server.address().port + "/";
27
- await execFileAsync(process.execPath, [
26
+ const { stderr } = await execFileAsync(process.execPath, [
28
27
  "single-file-node.js", url, outputPath,
29
28
  "--compress-content",
30
29
  "--output-json"
31
30
  ], { cwd: cliDirectory });
32
- const pageData = JSON.parse(await readFile(outputPath, "utf8"));
31
+ let rawPageData;
32
+ try {
33
+ rawPageData = await readFile(outputPath, "utf8");
34
+ } catch (error) {
35
+ throw new Error("missing output file, stderr: " + stderr, { cause: error });
36
+ }
37
+ const pageData = JSON.parse(rawPageData);
33
38
  assert.ok(pageData.binaryContent);
34
- const content = new TextDecoder("latin1").decode(Uint8Array.fromBase64(pageData.binaryContent));
39
+ const content = Buffer.from(pageData.binaryContent, "base64").toString("latin1");
35
40
  assert.ok(content.includes(ZIP_SIGNATURE));
36
41
  } finally {
37
42
  await rm(directory, { recursive: true });
@@ -0,0 +1,70 @@
1
+ /* global setInterval, clearInterval */
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, rm } from "node:fs/promises";
9
+ import { Buffer } from "node:buffer";
10
+ import { tmpdir } from "node:os";
11
+ import { join, dirname } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import process from "node:process";
14
+
15
+ const execFileAsync = promisify(execFile);
16
+ const cliDirectory = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
17
+
18
+ test("load timeout is reported when the page never loads", { timeout: 120000 }, async () => {
19
+ const server = createServer(() => { });
20
+ await new Promise(resolve => server.listen(0, "localhost", resolve));
21
+ const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
22
+ try {
23
+ const url = "http://localhost:" + server.address().port + "/";
24
+ const { stderr } = await runCli([
25
+ url, join(directory, "out.html"),
26
+ "--browser-load-max-time", "5000",
27
+ "--browser-wait-until-fallback", "false"
28
+ ]);
29
+ assert.ok(stderr.includes("Load timeout"), "stderr: " + stderr);
30
+ } finally {
31
+ await rm(directory, { recursive: true });
32
+ server.closeAllConnections();
33
+ server.close();
34
+ }
35
+ });
36
+
37
+ test("capture timeout is reported when capturing an endless download", { timeout: 120000 }, async () => {
38
+ const chunk = Buffer.alloc(65536);
39
+ const server = createServer((_, response) => {
40
+ response.writeHead(200, { "content-type": "application/octet-stream", "content-disposition": "attachment; filename=big.bin" });
41
+ const intervalId = setInterval(() => response.write(chunk), 50);
42
+ response.on("close", () => clearInterval(intervalId));
43
+ });
44
+ await new Promise(resolve => server.listen(0, "localhost", resolve));
45
+ const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
46
+ try {
47
+ const url = "http://localhost:" + server.address().port + "/";
48
+ const { stderr } = await runCli([
49
+ url, join(directory, "out.html"),
50
+ "--browser-load-max-time", "5000",
51
+ "--browser-capture-max-time", "10000"
52
+ ]);
53
+ assert.ok(stderr.includes("Capture timeout") || stderr.includes("Load timeout"), "stderr: " + stderr);
54
+ } finally {
55
+ await rm(directory, { recursive: true });
56
+ server.closeAllConnections();
57
+ server.close();
58
+ }
59
+ });
60
+
61
+ async function runCli(args) {
62
+ try {
63
+ return await execFileAsync(process.execPath, ["single-file-node.js", ...args], { cwd: cliDirectory, timeout: 90000, killSignal: "SIGKILL" });
64
+ } catch (error) {
65
+ if (error.killed) {
66
+ throw new Error("the process did not exit before the timeout");
67
+ }
68
+ return { stdout: error.stdout, stderr: error.stderr };
69
+ }
70
+ }
@@ -0,0 +1,37 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { createServer } from "node:http";
4
+ import { execFile } from "node:child_process";
5
+ import { promisify } from "node:util";
6
+ import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
7
+ import { tmpdir } from "node:os";
8
+ import { join, dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import process from "node:process";
11
+
12
+ const execFileAsync = promisify(execFile);
13
+ const cliDirectory = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
14
+
15
+ test("duplicate urls in a urls file are captured once", { timeout: 120000 }, async () => {
16
+ const server = createServer((_, response) => response
17
+ .writeHead(200, { "content-type": "text/html" })
18
+ .end("<html><head><title>Same Page</title></head><body>content</body></html>"));
19
+ await new Promise(resolve => server.listen(0, "localhost", resolve));
20
+ const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
21
+ try {
22
+ const url = "http://localhost:" + server.address().port + "/";
23
+ const urlsFilePath = join(directory, "urls.txt");
24
+ await writeFile(urlsFilePath, url + "\n\n" + url + "\n");
25
+ const { stderr } = await execFileAsync(process.execPath, [
26
+ "single-file-node.js",
27
+ "--urls-file", urlsFilePath,
28
+ "--output-directory", directory,
29
+ "--filename-template", "{page-title}.html"
30
+ ], { cwd: cliDirectory });
31
+ const files = (await readdir(directory)).filter(file => file.endsWith(".html"));
32
+ assert.deepEqual(files, ["Same Page.html"], "stderr: " + stderr);
33
+ } finally {
34
+ await rm(directory, { recursive: true });
35
+ server.close();
36
+ }
37
+ });
@@ -0,0 +1,56 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+ import { join, dirname } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import process from "node:process";
8
+
9
+ const execFileAsync = promisify(execFile);
10
+ const cliDirectory = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
11
+
12
+ async function runCli(args) {
13
+ try {
14
+ const { stdout, stderr } = await execFileAsync(process.execPath, ["single-file-node.js", ...args], { cwd: cliDirectory });
15
+ return { code: 0, stdout, stderr };
16
+ } catch (error) {
17
+ return { code: error.code, stdout: error.stdout, stderr: error.stderr };
18
+ }
19
+ }
20
+
21
+ test("help is displayed with an exit code of 0", async () => {
22
+ const { code, stdout } = await runCli(["--help"]);
23
+ assert.equal(code, 0);
24
+ assert.ok(stdout.includes("--browser-executable-path"));
25
+ });
26
+
27
+ test("a missing url is reported as an error", async () => {
28
+ const { code, stderr } = await runCli(["--browser-executable-path", "/path/to/chrome"]);
29
+ assert.equal(code, 1);
30
+ assert.ok(stderr.includes("The URL or path of the page to save is required"));
31
+ });
32
+
33
+ test("unknown options are reported as errors", async () => {
34
+ const { code, stderr } = await runCli(["--foo", "--bar", "https://example.com", "out.html"]);
35
+ assert.equal(code, 1);
36
+ assert.ok(stderr.includes("Unknown option --foo"));
37
+ assert.ok(stderr.includes("Unknown option --bar"));
38
+ });
39
+
40
+ test("invalid option values are reported as errors", async () => {
41
+ const { code, stderr } = await runCli(["--browser-width", "abc", "https://example.com"]);
42
+ assert.equal(code, 1);
43
+ assert.ok(stderr.includes("Invalid value for --browser-width: \"abc\""));
44
+ });
45
+
46
+ test("a wrong browser executable path is reported with the path", async () => {
47
+ const { code, stderr } = await runCli(["https://localhost:1/", "out.html", "--browser-executable-path", "/nonexistent/chrome"]);
48
+ assert.notEqual(code, 0);
49
+ assert.ok(stderr.includes("The browser executable was not found at \"/nonexistent/chrome\""));
50
+ });
51
+
52
+ test("unexpected extra arguments are reported as errors", async () => {
53
+ const { code, stderr } = await runCli(["https://example.com", "out.html", "extra.html"]);
54
+ assert.equal(code, 1);
55
+ assert.ok(stderr.includes("Unexpected arguments: extra.html"));
56
+ });
@@ -0,0 +1,136 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { parseArgs, applySettings, parseUrlsFile } from "../../options.js";
4
+
5
+ const parse = args => parseArgs(args).options;
6
+
7
+ test("boolean options with uppercase canonical names can be disabled", () => {
8
+ assert.equal(parse(["--compress-html", "false"]).compressHTML, false);
9
+ assert.equal(parse(["--compress-HTML", "false"]).compressHTML, false);
10
+ assert.equal(parse(["--crawl-remove-url-fragment", "false"]).crawlRemoveURLFragment, false);
11
+ assert.equal(parse(["--insert-meta-csp", "false"]).insertMetaCSP, false);
12
+ });
13
+
14
+ test("option names are canonicalized regardless of case", () => {
15
+ assert.equal(parse(["--include-bom"]).includeBOM, true);
16
+ assert.equal(parse(["--Include-BOM"]).includeBOM, true);
17
+ assert.equal(parse(["--crawl-replace-urls"]).crawlReplaceURLs, true);
18
+ assert.equal(parse(["--save-original-urls"]).saveOriginalURLs, true);
19
+ assert.deepEqual(parse(["--blocked-url-pattern", "ads"]).blockedURLPatterns, ["ads"]);
20
+ const options = parse(["--compress-html", "false"]);
21
+ assert.equal("compressHtml" in options, false);
22
+ });
23
+
24
+ test("aliases map to the canonical option", () => {
25
+ assert.equal(parse(["--error-file", "errors.txt"]).errorsFile, "errors.txt");
26
+ assert.equal(parse(["--errors-file", "errors.txt"]).errorsFile, "errors.txt");
27
+ assert.equal(parse(["--error-traces-disabled", "false"]).errorsTracesDisabled, false);
28
+ assert.equal(parse(["--errors-traces-disabled", "false"]).errorsTracesDisabled, false);
29
+ assert.equal(parse([]).errorsTracesDisabled, true);
30
+ });
31
+
32
+ test("browser arguments are merged", () => {
33
+ assert.deepEqual(parse(["--browser-arg", "--disable-gpu"]).browserArgs, ["--disable-gpu"]);
34
+ assert.deepEqual(parse(["--browser-args", "[\"--mute-audio\"]"]).browserArgs, ["--mute-audio"]);
35
+ assert.deepEqual(parse(["--browser-arg", "--disable-gpu", "--browser-args", "[\"--mute-audio\"]"]).browserArgs, ["--disable-gpu", "--mute-audio"]);
36
+ });
37
+
38
+ test("default values are applied under canonical keys", () => {
39
+ const options = parse([]);
40
+ assert.equal(options.compressHTML, true);
41
+ assert.equal(options.crawlRemoveURLFragment, true);
42
+ assert.equal(options.insertMetaCSP, true);
43
+ assert.equal(options.browserWidth, 1280);
44
+ });
45
+
46
+ test("invalid and missing values are reported", () => {
47
+ assert.deepEqual(parseArgs(["--browser-width", "abc"]).invalidOptions, [{ name: "browser-width", value: "abc" }]);
48
+ assert.deepEqual(parseArgs(["--browser-width=abc"]).invalidOptions, [{ name: "browser-width", value: "abc" }]);
49
+ assert.deepEqual(parseArgs(["--browser-height="]).invalidOptions, [{ name: "browser-height", value: "" }]);
50
+ assert.deepEqual(parseArgs(["--output-directory"]).invalidOptions, [{ name: "output-directory" }]);
51
+ assert.deepEqual(parseArgs(["--browser-width", "abc"]).positionals, []);
52
+ assert.deepEqual(parseArgs(["--browser-debug"]).invalidOptions, []);
53
+ });
54
+
55
+ test("the last value wins when a scalar option is repeated", () => {
56
+ const { options, positionals } = parseArgs(["--browser-width", "100", "--browser-width", "200"]);
57
+ assert.equal(options.browserWidth, 200);
58
+ assert.deepEqual(positionals, []);
59
+ assert.equal(parse(["--browser-width=100", "--browser-width=200"]).browserWidth, 200);
60
+ });
61
+
62
+ test("http headers keep characters after the first equals sign", () => {
63
+ const options = parse(["--http-header", "authorization=Basic dGVzdA==", "--http-header", " x-test = value "]);
64
+ assert.deepEqual(options.httpHeaders, { authorization: "Basic dGVzdA==", "x-test": "value" });
65
+ assert.deepEqual(parseArgs(["--http-header", "no-separator"]).invalidOptions, [{ name: "http-header", value: "no-separator" }]);
66
+ });
67
+
68
+ test("media features are split on the first colon", () => {
69
+ assert.deepEqual(parse(["--emulate-media-feature", "prefers-color-scheme:dark"]).emulateMediaFeatures, [{ name: "prefers-color-scheme", value: "dark" }]);
70
+ assert.deepEqual(parseArgs(["--emulate-media-feature", "no-separator"]).invalidOptions, [{ name: "emulate-media-feature", value: "no-separator" }]);
71
+ });
72
+
73
+ test("browser cookies are parsed into objects", () => {
74
+ const options = parse(["--browser-cookie", "name,value,example.com,/,,true,true,None,https://example.com"]);
75
+ assert.deepEqual(options.browserCookies, [{
76
+ name: "name",
77
+ value: "value",
78
+ url: "https://example.com",
79
+ domain: "example.com",
80
+ path: "/",
81
+ secure: true,
82
+ httpOnly: true,
83
+ sameSite: "None",
84
+ expires: undefined
85
+ }]);
86
+ assert.equal(parse(["--browser-cookie", "name,value,example.com,/,1893456000,false,false,Lax,https://example.com"]).browserCookies[0].expires, 1893456000);
87
+ });
88
+
89
+ test("invalid browser args json is reported", () => {
90
+ assert.deepEqual(parseArgs(["--browser-args", "not json"]).invalidOptions, [{ name: "browser-args", value: "not json" }]);
91
+ });
92
+
93
+ test("explicit options win over settings file profiles", () => {
94
+ const options = parse(["--settings-file-profile", "custom", "--browser-width", "1024"]);
95
+ const settings = { profiles: { custom: { compressHTML: false, browserWidth: 800 } } };
96
+ applySettings(options, settings, { browserWidth: 1024 });
97
+ assert.equal(options.compressHTML, false);
98
+ assert.equal(options.browserWidth, 1024);
99
+ });
100
+
101
+ test("the default profile maps to the default settings", () => {
102
+ const options = parse([]);
103
+ applySettings(options, { profiles: { __Default_Settings__: { compressHTML: false } } }, {});
104
+ assert.equal(options.compressHTML, false);
105
+ });
106
+
107
+ test("an unknown settings profile is reported", () => {
108
+ const options = parse(["--settings-file-profile", "missing"]);
109
+ assert.throws(
110
+ () => applySettings(options, { profiles: { __Default_Settings__: {}, work: {} } }, {}),
111
+ /Unknown profile "missing", available profiles: work/);
112
+ });
113
+
114
+ test("blank lines in urls files are skipped", () => {
115
+ assert.deepEqual(parseUrlsFile("https://a.example\n\n \nhttps://b.example\r\n"), ["https://a.example", "https://b.example"]);
116
+ });
117
+
118
+ test("single character values in urls file options are kept", () => {
119
+ const [url, options] = parseUrlsFile("https://a.example --crawl-max-depth 2 --browser-width 100")[0];
120
+ assert.equal(url, "https://a.example");
121
+ assert.equal(options.crawlMaxDepth, 2);
122
+ assert.equal(options.browserWidth, 100);
123
+ });
124
+
125
+ test("quoted and equal form values in urls files are parsed", () => {
126
+ const [, options] = parseUrlsFile("https://a.example --filename-template \"My Page.html\" --browser-height=600")[0];
127
+ assert.equal(options.filenameTemplate, "My Page.html");
128
+ assert.equal(options.browserHeight, 600);
129
+ });
130
+
131
+ test("values are parsed from both spaced and equal forms", () => {
132
+ assert.equal(parse(["--browser-width", "1024"]).browserWidth, 1024);
133
+ assert.equal(parse(["--browser-width=1024"]).browserWidth, 1024);
134
+ assert.equal(parse(["--browser-headless=false"]).browserHeadless, false);
135
+ assert.equal(parse(["--filename-template={page-title}.html"]).filenameTemplate, "{page-title}.html");
136
+ });
@@ -0,0 +1,37 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, rm } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { Deno } from "../../lib/deno-polyfill.js";
7
+
8
+ const { writeTextFile, writeFile, readTextFile, errors } = Deno;
9
+
10
+ test("exclusive creation fails when the file exists", async () => {
11
+ const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
12
+ try {
13
+ const filename = join(directory, "out.txt");
14
+ await writeTextFile(filename, "first", { createNew: true });
15
+ await assert.rejects(
16
+ () => writeTextFile(filename, "second", { createNew: true }),
17
+ error => error instanceof errors.AlreadyExists);
18
+ await assert.rejects(
19
+ () => writeFile(filename, new Uint8Array([1]), { createNew: true }),
20
+ error => error instanceof errors.AlreadyExists);
21
+ assert.equal(await readTextFile(filename), "first");
22
+ } finally {
23
+ await rm(directory, { recursive: true });
24
+ }
25
+ });
26
+
27
+ test("writing without exclusive creation overwrites", async () => {
28
+ const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
29
+ try {
30
+ const filename = join(directory, "out.txt");
31
+ await writeTextFile(filename, "first");
32
+ await writeTextFile(filename, "second");
33
+ assert.equal(await readTextFile(filename), "second");
34
+ } finally {
35
+ await rm(directory, { recursive: true });
36
+ }
37
+ });