single-file-cli 2.9.0 → 2.9.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.
@@ -29,6 +29,8 @@ import { Deno } from "./deno-polyfill.js";
29
29
  const FETCH_FUNCTION_NAME = "__singleFileFetch";
30
30
  const RESOLVE_FETCH_FUNCTION_NAME = "__singleFileResolveFetch";
31
31
  const REJECT_FETCH_FUNCTION_NAME = "__singleFileRejectFetch";
32
+ const SCRIPT_READY_PROPERTY_NAME = "__singleFileScriptReady";
33
+ const SCRIPT_ERROR_PROPERTY_NAME = "__singleFileScriptError";
32
34
 
33
35
  const { readTextFile } = Deno;
34
36
 
@@ -36,6 +38,8 @@ export {
36
38
  FETCH_FUNCTION_NAME,
37
39
  RESOLVE_FETCH_FUNCTION_NAME,
38
40
  REJECT_FETCH_FUNCTION_NAME,
41
+ SCRIPT_READY_PROPERTY_NAME,
42
+ SCRIPT_ERROR_PROPERTY_NAME,
39
43
  getScriptSource,
40
44
  getHookScriptSource,
41
45
  getZipScriptSource,
@@ -101,7 +105,8 @@ async function getScriptSource(options) {
101
105
  RESOLVE_FETCH_FUNCTION_NAME,
102
106
  REJECT_FETCH_FUNCTION_NAME
103
107
  }) + ");";
104
- return source;
108
+ return "try{" + source + "\nglobalThis." + SCRIPT_READY_PROPERTY_NAME + "=true;}" +
109
+ "catch(error){globalThis." + SCRIPT_ERROR_PROPERTY_NAME + "=String(error);throw error;}";
105
110
  }
106
111
 
107
112
  function getHookScriptSource() {
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const version = "2.9.0";
1
+ export const version = "2.9.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-cli",
3
- "version": "2.9.0",
3
+ "version": "2.9.2",
4
4
  "description": "SingleFile CLI",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -0,0 +1,72 @@
1
+ // The injected script is one concatenation: the SingleFile bundle, then the scripts named by
2
+ // --browser-script, then the call that hands SingleFile its options and its fetch fallback. A
3
+ // throw anywhere in it stops the rest, and nothing reported it — the browser does not surface an
4
+ // exception raised by a preload script, and the only guard was "is `singlefile` defined", which is
5
+ // already true by then. So a script that threw produced a save that looked ordinary and was
6
+ // missing the fetch fallback the CLI installs for the resources the page itself cannot get.
7
+
8
+ import { test } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { createServer } from "node:http";
11
+ import { execFile } from "node:child_process";
12
+ import { promisify } from "node:util";
13
+ import { mkdtemp, readFile, writeFile, rm } from "node:fs/promises";
14
+ import { existsSync } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { join } from "node:path";
17
+ import process from "node:process";
18
+ import { cliDirectory } from "../target.js";
19
+
20
+ const execFileAsync = promisify(execFile);
21
+ const TEST_TIMEOUT = 120000;
22
+
23
+ test("a browser script runs in the page it saves", { timeout: TEST_TIMEOUT }, async () => {
24
+ const MARKER = "browser-script-marker";
25
+ await withPage(async ({ url, directory }) => {
26
+ const scriptPath = join(directory, "marker.js");
27
+ await writeFile(scriptPath, "addEventListener(\"DOMContentLoaded\",()=>{" +
28
+ "const marker=document.createElement(\"p\");marker.id=\"" + MARKER + "\";document.body.appendChild(marker);});");
29
+ const outputPath = join(directory, "out.html");
30
+ await execFileAsync(process.execPath, [
31
+ "single-file-node.js", url, outputPath,
32
+ "--browser-script", scriptPath
33
+ ], { cwd: cliDirectory });
34
+ assert.match(await readFile(outputPath, "utf8"), new RegExp(MARKER));
35
+ });
36
+ });
37
+
38
+ test("a browser script that throws fails the save instead of degrading it", { timeout: TEST_TIMEOUT }, async () => {
39
+ const SCRIPT_ERROR_MESSAGE = "browser script boom";
40
+ await withPage(async ({ url, directory }) => {
41
+ const scriptPath = join(directory, "throwing.js");
42
+ await writeFile(scriptPath, "throw new Error(\"" + SCRIPT_ERROR_MESSAGE + "\");");
43
+ const outputPath = join(directory, "out.html");
44
+ let exitCode = 0, stderr = "";
45
+ try {
46
+ await execFileAsync(process.execPath, [
47
+ "single-file-node.js", url, outputPath,
48
+ "--browser-script", scriptPath
49
+ ], { cwd: cliDirectory });
50
+ } catch (error) {
51
+ ({ code: exitCode, stderr } = error);
52
+ }
53
+ assert.notEqual(exitCode, 0, "a save whose injected script threw was reported as a success");
54
+ // the message the script threw is what says which script to look at: without it the
55
+ // failure names the execution context and leaves the cause to be guessed
56
+ assert.match(stderr, new RegExp(SCRIPT_ERROR_MESSAGE), "the failure does not name the error the script threw");
57
+ assert.ok(!existsSync(outputPath), "a page was written for a save that failed");
58
+ });
59
+ });
60
+
61
+ async function withPage(run) {
62
+ const server = createServer((request, response) =>
63
+ response.writeHead(200, { "content-type": "text/html" }).end("<html><body><h1>page</h1></body></html>"));
64
+ await new Promise(resolve => server.listen(0, "localhost", resolve));
65
+ const directory = await mkdtemp(join(tmpdir(), "single-file-test-"));
66
+ try {
67
+ await run({ url: "http://localhost:" + server.address().port + "/", directory });
68
+ } finally {
69
+ await rm(directory, { recursive: true, force: true });
70
+ server.close();
71
+ }
72
+ }
@@ -24,7 +24,7 @@ import { tmpdir } from "node:os";
24
24
  import { join, dirname } from "node:path";
25
25
  import { pathToFileURL, fileURLToPath } from "node:url";
26
26
  import process from "node:process";
27
- import { cliDirectory, importLibModule, useDevBuild, firefox } from "../target.js";
27
+ import { cliDirectory, importLibModule, useDevBuild } from "../target.js";
28
28
  import { openBrowser } from "../fidelity/browser.js";
29
29
  import { startServer } from "../fidelity/server.js";
30
30
  const { configure, ZipReader, Uint8ArrayReader, TextWriter } = await importLibModule("single-file-archive.js");
@@ -134,7 +134,7 @@ function getDeclaredFontFamilies(saved) {
134
134
  });
135
135
  }
136
136
 
137
- test("a saved page keeps the fonts declared inside a frame it cannot read", { ...options, skip: skip || (firefox && "the font declared by a sandboxed srcdoc frame is lost on Firefox, see the SingleFile backlog") }, async () => {
137
+ test("a saved page keeps the fonts declared inside a frame it cannot read", options, async () => {
138
138
  const { comparison, noise } = await compareSaveWithSource("frame-fonts", []);
139
139
  assertNoWorseThanNoise(comparison, noise);
140
140
  });