single-file-cli 2.0.77 → 2.0.78

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.
@@ -125,9 +125,11 @@ const pathAPI = {
125
125
  SEPARATOR: DENO_RUNTIME_DETECTED ? path.SEPARATOR : path.sep
126
126
  };
127
127
 
128
+ const isDeno = DENO_RUNTIME_DETECTED;
129
+
128
130
  await initGlobalThisProperties();
129
131
 
130
- export { DenoAPI as Deno, pathAPI as path };
132
+ export { DenoAPI as Deno, pathAPI as path, isDeno };
131
133
 
132
134
  async function initGlobalThisProperties() {
133
135
  if (!DENO_RUNTIME_DETECTED) {
@@ -21,7 +21,7 @@
21
21
  * Source.
22
22
  */
23
23
 
24
- /* global singlefile, atob, Headers */
24
+ /* global window, atob, fetch, Headers */
25
25
 
26
26
  import { script, hookScript, zipScript } from "../lib/single-file-bundle.js";
27
27
  import { Deno } from "./deno-polyfill.js";
@@ -38,7 +38,8 @@ export {
38
38
  REJECT_FETCH_FUNCTION_NAME,
39
39
  getScriptSource,
40
40
  getHookScriptSource,
41
- getZipScriptSource
41
+ getZipScriptSource,
42
+ getPageDataScriptSource
42
43
  };
43
44
 
44
45
  function initSingleFile(constants) {
@@ -71,7 +72,7 @@ function initSingleFile(constants) {
71
72
  }
72
73
  };
73
74
 
74
- singlefile.init({
75
+ window.singlefile.init({
75
76
  fetch: async (url, options) => {
76
77
  try {
77
78
  return await nativeFetch(url, options);
@@ -109,6 +110,49 @@ function getZipScriptSource() {
109
110
  return zipScript;
110
111
  }
111
112
 
113
+
114
+ function getPageDataScriptSource(options, [SET_SCREENSHOT_FUNCTION_NAME, SET_PDF_FUNCTION_NAME, SET_PAGE_DATA_FUNCTION_NAME, CAPTURE_SCREENSHOT_FUNCTION_NAME, PRINT_TO_PDF_FUNCTION_NAME]) {
115
+ if ((options.embedScreenshot || options.embedPdf) && options.compressContent) {
116
+ let screenshot, pdf;
117
+ if (options.embedScreenshot) {
118
+ screenshot = new Promise(resolve => globalThis[SET_SCREENSHOT_FUNCTION_NAME] = async data =>
119
+ resolve(Array.from(new Uint8Array(await (await fetch("data:image/png;base64," + data)).arrayBuffer()))));
120
+ }
121
+ if (options.embedPdf) {
122
+ pdf = new Promise(resolve => globalThis[SET_PDF_FUNCTION_NAME] = async data =>
123
+ resolve(Array.from(new Uint8Array(await (await fetch("data:application/pdf;base64," + data)).arrayBuffer()))));
124
+ }
125
+ let pendingCapture;
126
+ options.onprogress = async event => {
127
+ if (event.type == event.RESOURCES_INITIALIZING && window == window.top && !pendingCapture) {
128
+ pendingCapture = true;
129
+ if (globalThis[CAPTURE_SCREENSHOT_FUNCTION_NAME]) {
130
+ globalThis[CAPTURE_SCREENSHOT_FUNCTION_NAME]("");
131
+ options.embeddedImage = await screenshot;
132
+ }
133
+ if (globalThis[PRINT_TO_PDF_FUNCTION_NAME]) {
134
+ globalThis[PRINT_TO_PDF_FUNCTION_NAME]("");
135
+ options.embeddedPdf = await pdf;
136
+ }
137
+ }
138
+ };
139
+ }
140
+ const MAX_CONTENT_SIZE = 32 * 1024 * 1024;
141
+ return window.singlefile.getPageData(options).then(data => {
142
+ if (data.content instanceof Uint8Array) {
143
+ data.content = Array.from(data.content);
144
+ }
145
+ data = JSON.stringify(data);
146
+ let indexData = 0;
147
+ do {
148
+ globalThis[SET_PAGE_DATA_FUNCTION_NAME](data.slice(indexData, indexData + MAX_CONTENT_SIZE));
149
+ indexData += MAX_CONTENT_SIZE;
150
+ } while (indexData < data.length);
151
+ globalThis[SET_PAGE_DATA_FUNCTION_NAME]("");
152
+ });
153
+ }
154
+
155
+
112
156
  async function readScriptFiles(paths) {
113
157
  return (await Promise.all(paths.map(path => readTextFile(path)))).join("");
114
158
  }
package/lib/version.js CHANGED
@@ -1 +1 @@
1
- export const version = "2.0.77";
1
+ export const version = "2.0.78";
package/options.js CHANGED
@@ -30,117 +30,102 @@ Positionals:
30
30
  url URL or path on the filesystem of the page to save [string]
31
31
  output Output filename [string]`;
32
32
 
33
- const OPTIONS_INFO = {
34
- "accept-header-font": { description: "Accept header for fonts", type: "string", defaultValue: "application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8" },
35
- "accept-header-image": { description: "Accept header for images", type: "string", defaultValue: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" },
36
- "accept-header-stylesheet": { description: "Accept header for stylesheets", type: "string", defaultValue: "text/css,*/*;q=0.1" },
37
- "accept-header-script": { description: "Accept header for scripts", type: "string", defaultValue: "*/*" },
38
- "accept-header-document": { description: "Accept header for documents", type: "string", defaultValue: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" },
39
- "block-audios": { description: "Block audios", type: "boolean", defaultValue: true },
40
- "block-fonts": { description: "Block fonts", type: "boolean" },
41
- "block-images": { description: "Block images", type: "boolean" },
42
- "block-alternative-images": { description: "Block alternative images", type: "boolean", defaultValue: true },
43
- "block-scripts": { description: "Block scripts", type: "boolean", defaultValue: true },
44
- "block-stylesheets": { description: "Block stylesheets", type: "boolean", defaultValue: false },
45
- "block-videos": { description: "Block videos", type: "boolean", defaultValue: true },
46
- "block-mixed-content": { description: "Block mixed contents", type: "boolean" },
47
- "blocked-URL-pattern": { description: "Regular expression matching URLs to block (e.g. 'annoying-banners\\.com')", type: "string[]" },
33
+ const CATEGORIES = [
34
+ "Browser configuration",
35
+ "Browser Loading & Timing",
36
+ "Device Emulation",
37
+ "Network & HTTP",
38
+ "Content Blocking",
39
+ "Resource Loading",
40
+ "Content Processing & Optimization",
41
+ "Output & Compression",
42
+ "Filename & Output Location",
43
+ "Crawling",
44
+ "Browser Customization & Scripts",
45
+ "Infobar",
46
+ "Metadata & HTML Modifications",
47
+ "Debugging & Logging",
48
+ "Batch Processing & Configuration",
49
+ "Advanced/Special Modes",
50
+ "General"
51
+ ];
52
+
53
+ const OPTIONS_INFO = [{
48
54
  "browser-server": { description: "Server to connect to", type: "string" },
49
55
  "browser-headless": { description: "Run the browser in headless mode", type: "boolean", defaultValue: true },
50
56
  "browser-executable-path": { description: "Path to chrome/chromium executable", type: "string" },
51
57
  "browser-width": { description: "Width of the browser viewport in pixels", type: "number", defaultValue: 1280 },
52
58
  "browser-height": { description: "Height of the browser viewport in pixels", type: "number", defaultValue: 720 },
59
+ "browser-debug": { description: "Enable debug mode", type: "boolean" },
60
+ "browser-arg": { description: "Argument passed to the browser", type: "string[]", alias: "browser-argument" },
61
+ "browser-args": { description: "Arguments provided as a JSON array and passed to the browser", type: "string" },
62
+ "browser-start-minimized": { description: "Minimize the browser", type: "boolean" },
63
+ "browser-ignore-insecure-certs": { description: "Ignore HTTPs errors", type: "boolean" },
64
+ "browser-remote-debugging-URL": { description: "Remote debugging URL", type: "string" }
65
+ }, {
53
66
  "browser-load-max-time": { description: "Maximum delay of time to wait for loading the page in ms", type: "number", defaultValue: 60000 },
54
67
  "browser-capture-max-time": { description: "Maximum delay of time to wait for capturing the page in ms", type: "number", defaultValue: 60000 },
55
68
  "browser-wait-delay": { description: "Time to wait before capturing the page in ms", type: "number" },
69
+ "browser-wait-end-delay": { description: "Time to wait after capturing the page in ms", type: "number" },
56
70
  "browser-wait-until": { description: "When to consider the page is loaded (InteractiveTime, networkIdle, networkAlmostIdle, load, domContentLoaded)", type: "string", defaultValue: "networkIdle" },
57
71
  "browser-wait-until-delay": { description: "Delay of time in ms to wait before considering the page is loaded when the value of --browser-wait-until is reached", type: "number", defaultValue: 1000 },
58
72
  "browser-wait-until-fallback": { description: "Retry with the next value of --browser-wait-until when a timeout error is thrown", type: "boolean", defaultValue: true },
59
- "browser-debug": { description: "Enable debug mode", type: "boolean" },
60
- "browser-script": { description: "Path of a script executed in the page (and all the frames) before it is loaded", type: "string[]" },
61
- "browser-stylesheet": { description: "Path of a stylesheet file inserted into the page (and all the frames) after it is loaded", type: "string[]" },
62
- "browser-arg": { description: "Argument passed to the browser", type: "string[]", alias: "browser-argument" },
63
- "browser-args": { description: "Arguments provided as a JSON array and passed to the browser", type: "string" },
64
- "browser-start-minimized": { description: "Minimize the browser", type: "boolean" },
65
- "browser-cookie": { description: "Ordered list of cookie parameters separated by a comma (name,value,domain,path,expires,httpOnly,secure,sameSite,url)", type: "string[]" },
66
- "browser-cookies-file": { description: "Path of the cookies file formatted as a JSON file or a Netscape text file", type: "string" },
67
- "browser-ignore-insecure-certs": { description: "Ignore HTTPs errors", type: "boolean" },
68
- "browser-remote-debugging-URL": { description: "Remote debugging URL", type: "string" },
73
+ }, {
69
74
  "browser-mobile-emulation": { description: "Emulate a mobile device", type: "boolean" },
70
75
  "browser-device-width": { description: "Width of the device viewport in pixels (default value is 360 when using --browser-mobile-emulation)", type: "number" },
71
76
  "browser-device-height": { description: "Height of the device viewport in pixels (default value is 800 when using --browser-mobile-emulation)", type: "number" },
72
77
  "browser-device-scale-factor": { description: "Scale factor of the device viewport (default value is 2 when using --browser-mobile-emulation)", type: "number" },
73
- "console-messages-file": { description: "Path of the file where to save the console messages in JSON format", type: "string" },
74
- "debug-messages-file": { description: "Path of the file where to save the debug messages", type: "string" },
75
- "compress-CSS": { description: "Compress CSS stylesheets", type: "boolean" },
76
- "compress-HTML": { description: "Compress HTML content", type: "boolean", defaultValue: true },
77
- "crawl-links": { description: "Crawl and save pages found via inner links", type: "boolean" },
78
- "crawl-inner-links-only": { description: "Crawl pages found via inner links only if they are hosted on the same domain", type: "boolean", defaultValue: true },
79
- "crawl-no-parent": { description: "Crawl pages found via inner links only if their URLs are not parent of the URL to crawl", type: "boolean" },
80
- "crawl-load-session": { description: "Name of the file of the session to load (previously saved with --crawl-save-session or --crawl-sync-session)", type: "string" },
81
- "crawl-remove-URL-fragment": { description: "Remove URL fragments found in links", type: "boolean", defaultValue: true },
82
- "crawl-save-session": { description: "Name of the file where to save the state of the session", type: "string" },
83
- "crawl-sync-session": { description: "Name of the file where to load and save the state of the session", type: "string" },
84
- "crawl-max-depth": { description: "Max depth when crawling pages found in internal and external links (0: infinite)", type: "number", defaultValue: 1 },
85
- "crawl-external-links-max-depth": { description: "Max depth when crawling pages found in external links (0: infinite)", type: "number", defaultValue: 1 },
86
- "crawl-replace-URLs": { description: "Replace URLs of saved pages with relative paths of saved pages on the filesystem", type: "boolean" },
87
- "crawl-rewrite-rule": { description: "Rewrite rule used to rewrite URLs of crawled pages", type: "string[]" },
88
- "dump-content": { description: "Dump the content of the processed page in the console ('true' when running in Docker)", type: "boolean" },
89
- "emulate-media-feature": { description: "Emulate a media feature. The syntax is <name>:<value>, e.g. \"prefers-color-scheme:dark\"", type: "string[]" },
90
- "errors-file": { description: "Path of the file where to save the error messages", type: "string", alias: "error-file" },
91
- "errors-traces-disabled": { description: "Remove error stack traces in the error messages", type: "boolean", defaultValue: true, alias: "error-traces-disabled" },
92
- "filename-template": { description: "Template used to generate the output filename (see help page of the extension for more info)", type: "string", defaultValue: "%if-empty<{page-title}|No title> ({date-locale} {time-locale}).{filename-extension}" },
93
- "filename-conflict-action": { description: "Action when the filename is conflicting with existing one on the filesystem. The possible values are \"uniquify\" (default), \"overwrite\" and \"skip\"", type: "string", defaultValue: "uniquify" },
94
- "filename-replacement-character": { description: "The character used for replacing invalid characters in filenames", type: "string", defaultValue: "_" },
95
- "filename-replaced-character": { description: "The replaced character and the character(s) used for replacing the replacement character in filenames separated by a space, e.g. --filename-replaced-character \">\" _GT_ to replace \">\" with _GT_", type: "string[]", defaultValue: ["~ ~", "+ +", "? ?", "% %", "* *", ": :", "| |", "\" "", "< <", "> >", "\\\\ \", "\\x00-\\x1f _", "\x7F _"] },
96
- "filename-max-length": { description: "Specify the maximum length of the filename", type: "number", defaultValue: 192 },
97
- "filename-max-length-unit": { description: "Specify the unit of the maximum length of the filename ('bytes' or 'chars')", type: "string", defaultValue: "bytes" },
98
- "replace-emojis-in-filename": { description: "Replace emojis in the filename with their unicode text representation", type: "boolean" },
99
- "group-duplicate-images": { description: "Group duplicate images into CSS custom properties", type: "boolean", defaultValue: true },
100
- "max-size-duplicate-images": { description: "Maximum size in bytes of duplicate images stored as CSS custom properties", type: "number", defaultValue: 512 * 1024 },
101
- "help": { description: "Show help", type: "boolean" },
78
+ "user-agent": { description: "User-agent of the browser", type: "string" },
79
+ "accept-language": { description: "Accept language of the browser", type: "string" },
80
+ "platform": { description: "Platform of the browser (default value is \"Android\" when using --browser-mobile-emulation)", type: "string" },
81
+ "emulate-media-feature": { description: "Emulate a media feature. The syntax is <name>:<value>, e.g. \"prefers-color-scheme:dark\"", type: "string[]" }
82
+ }, {
102
83
  "http-header": { description: "Extra HTTP header", type: "string[]" },
103
84
  "http-proxy-server": { description: "Proxy address", type: "string" },
104
85
  "http-proxy-username": { description: "HTTP username", type: "string" },
105
86
  "http-proxy-password": { description: "HTTP password", type: "string" },
106
- "include-BOM": { description: "Include the UTF-8 BOM into the HTML page", type: "boolean" },
107
- "include-infobar": { description: "Include the infobar", type: "boolean" },
108
- "infobar-template": { description: "Template used to generate the infobar content (see help page of the extension for more info)", type: "string" },
109
- "open-infobar": { description: "Keep the infobar open when using --include-infobar", type: "boolean" },
110
- "infobar-position-absolute": { description: "Position the infobar absolutely (fixed otherwise)", type: "boolean" },
111
- "infobar-position-top": { description: "Position the infobar at the top of the page", type: "string", defaultValue: "16px" },
112
- "infobar-position-bottom": { description: "Position the infobar at the bottom of the page", type: "string", defaultValue: "" },
113
- "infobar-position-right": { description: "Position the infobar at the right of the page", type: "string", defaultValue: "16px" },
114
- "infobar-position-left": { description: "Position the infobar at the left of the page", type: "string", defaultValue: "" },
115
- "insert-meta-CSP": { description: "Include a <meta> tag with a CSP to avoid potential requests to internet when viewing a page", type: "boolean", defaultValue: true },
87
+ "accept-header-font": { description: "Accept header for fonts", type: "string", defaultValue: "application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8" },
88
+ "accept-header-image": { description: "Accept header for images", type: "string", defaultValue: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" },
89
+ "accept-header-stylesheet": { description: "Accept header for stylesheets", type: "string", defaultValue: "text/css,*/*;q=0.1" },
90
+ "accept-header-script": { description: "Accept header for scripts", type: "string", defaultValue: "*/*" },
91
+ "accept-header-document": { description: "Accept header for documents", type: "string", defaultValue: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
92
+ }, {
93
+ "block-audios": { description: "Block audios", type: "boolean", defaultValue: true },
94
+ "block-fonts": { description: "Block fonts", type: "boolean" },
95
+ "block-images": { description: "Block images", type: "boolean" },
96
+ "block-alternative-images": { description: "Block alternative images", type: "boolean", defaultValue: true },
97
+ "block-scripts": { description: "Block scripts", type: "boolean", defaultValue: true },
98
+ "block-stylesheets": { description: "Block stylesheets", type: "boolean", defaultValue: false },
99
+ "block-videos": { description: "Block videos", type: "boolean", defaultValue: true },
100
+ "block-mixed-content": { description: "Block mixed contents", type: "boolean" },
101
+ "blocked-URL-pattern": { description: "Regular expression matching URLs to block (e.g. 'annoying-banners\\.com')", type: "string[]" }
102
+ }, {
116
103
  "load-deferred-images": { description: "Load deferred (a.k.a. lazy-loaded) images", type: "boolean", defaultValue: true },
117
104
  "load-deferred-images-dispatch-scroll-event": { description: "Dispatch 'scroll' event when loading deferred images", type: "boolean" },
118
105
  "load-deferred-images-max-idle-time": { description: "Maximum delay of time to wait for deferred images in ms", type: "number", defaultValue: 1500 },
119
106
  "load-deferred-images-keep-zoom-level": { description: "Load deferred images by keeping zoomed out the page", type: "boolean" },
120
107
  "load-deferred-images-before-frames": { description: "Load deferred frames before before saving fame contents", type: "boolean" },
121
- "max-parallel-workers": { description: "Maximum number of browsers launched in parallel when processing a list of URLs (cf --urls-file)", type: "number", defaultValue: 8 },
122
108
  "max-resource-size-enabled": { description: "Enable removal of embedded resources exceeding a given size", type: "boolean" },
123
- "max-resource-size": { description: "Maximum size of embedded resources in MB (i.e. images, stylesheets, scripts and iframes)", type: "number", defaultValue: 10 },
124
- "move-styles-in-head": { description: "Move style elements outside the head element into the head element", type: "boolean" },
125
- "password": { description: "Password of the zip file when using --compress-content or --self-extracting-archive", type: "string" },
109
+ "max-resource-size": { description: "Maximum size of embedded resources in MB (i.e. images, stylesheets, scripts and iframes)", type: "number", defaultValue: 10 }
110
+ }, {
111
+ "compress-CSS": { description: "Compress CSS stylesheets", type: "boolean" },
112
+ "compress-HTML": { description: "Compress HTML content", type: "boolean", defaultValue: true },
126
113
  "remove-frames": { description: "Remove frames", type: "boolean" },
127
114
  "remove-hidden-elements": { description: "Remove HTML elements which are not displayed", type: "boolean", defaultValue: true },
128
115
  "remove-unused-styles": { description: "Remove unused CSS rules and unneeded declarations", type: "boolean", defaultValue: true },
129
116
  "remove-unused-fonts": { description: "Remove unused CSS font rules", type: "boolean", defaultValue: true },
130
- "remove-saved-date": { description: "Remove saved date metadata in HTML header", type: "boolean" },
131
117
  "remove-alternative-fonts": { description: "Remove alternative fonts to the ones displayed", type: "boolean", defaultValue: true },
132
118
  "remove-alternative-medias": { description: "Remove alternative CSS stylesheets", type: "boolean", defaultValue: true },
133
119
  "remove-alternative-images": { description: "Remove images for alternative sizes of screen", type: "boolean", defaultValue: true },
134
120
  "remove-no-script-tags": { description: "Remove <noscript> tags", type: "boolean", defaultValue: true },
135
- "save-original-URLs": { description: "Save the original URLS in the embedded contents", type: "boolean" },
136
- "save-raw-page": { description: "Save the original page without interpreting it into the browser", type: "boolean" },
137
- "urls-file": { description: "Path to a text file containing a list of URLs (separated by a newline) to save. You can also pass the options after each URL (e.g. 'https://www.example.com --filename-template={page-title}.html')", type: "string" },
138
- "user-agent": { description: "User-agent of the browser", type: "string" },
139
- "accept-language": { description: "Accept language of the browser", type: "string" },
140
- "platform": { description: "Platform of the browser (default value is \"Android\" when using --browser-mobile-emulation)", type: "string" },
141
- "user-script-enabled": { description: "Enable the event API allowing to execute scripts before the page is saved", type: "boolean", defaultValue: true },
121
+ "move-styles-in-head": { description: "Move style elements outside the head element into the head element", type: "boolean" },
122
+ "group-duplicate-images": { description: "Group duplicate images into CSS custom properties", type: "boolean", defaultValue: true },
123
+ "max-size-duplicate-images": { description: "Maximum size in bytes of duplicate images stored as CSS custom properties", type: "number", defaultValue: 512 * 1024 },
124
+ "group-duplicate-stylesheets": { description: "Group duplicate inline stylesheets into a single stylesheet in order to reduce the size of the page", type: "boolean", defaultValue: false }
125
+ }, {
142
126
  "compress-content": { description: "Create a ZIP file instead of an HTML file", type: "boolean" },
143
127
  "self-extracting-archive": { description: "Create a self-extracting (ZIP) HTML file", type: "boolean", defaultValue: true },
128
+ "password": { description: "Password of the zip file when using --compress-content or --self-extracting-archive", type: "string" },
144
129
  "insert-text-body": { description: "Insert the text of the page into the self-extracting HTML file", type: "boolean" },
145
130
  "create-root-directory": { description: "Create a root directory based on the timestamp", type: "boolean" },
146
131
  "extract-data-from-page": { description: "Extract compressed data from the page instead of fetching the page in order to create universal self-extracting HTML files", type: "boolean", defaultValue: true },
@@ -150,16 +135,70 @@ const OPTIONS_INFO = {
150
135
  "embedded-image": { description: "Path to a PNG image to embed in the compressed file.", type: "string" },
151
136
  "embed-pdf": { description: "Embed a PDF file in the ZIP or self-extracting file. When enabled, the resulting file can be read as a ZIP file or a PDF file.", type: "boolean" },
152
137
  "embed-pdf-options": { description: "Options passed to the CDP method `Page.printToPDF()` given as a JSON string (e.g. { \"pageRanges\": \"1-1\", \"paperWidth\": 11, \"paperHeight\": 8.5 })", type: "string" },
153
- "embedded-pdf": { description: "Path to a PDF file to embed in the compressed file.", type: "string" },
154
- "output-directory": { description: "Path to where to save files, this path must exist.", type: "string" },
155
- "version": { description: "Print the version number and exit.", type: "boolean" },
156
- "output-json": { description: "Output the result as a JSON string containing the page and network info", type: "boolean" },
138
+ "embedded-pdf": { description: "Path to a PDF file to embed in the compressed file.", type: "string" }
139
+ }, {
140
+ "filename-template": { description: "Template used to generate the output filename (see help page of the extension for more info)", type: "string", defaultValue: "%if-empty<{page-title}|No title> ({date-locale} {time-locale}).{filename-extension}" },
141
+ "filename-conflict-action": { description: "Action when the filename is conflicting with existing one on the filesystem. The possible values are \"uniquify\" (default), \"overwrite\" and \"skip\"", type: "string", defaultValue: "uniquify" },
142
+ "filename-replacement-character": { description: "The character used for replacing invalid characters in filenames", type: "string", defaultValue: "_" },
143
+ "filename-replaced-character": { description: "The replaced character and the character(s) used for replacing the replacement character in filenames separated by a space, e.g. --filename-replaced-character \">\" _GT_ to replace \">\" with _GT_", type: "string[]", defaultValue: ["~ ~", "+ +", "? ?", "% %", "* *", ": :", "| |", "\" "", "< <", "> >", "\\\\ \", "\\x00-\\x1f _", "\x7F _"] },
144
+ "filename-max-length": { description: "Specify the maximum length of the filename", type: "number", defaultValue: 192 },
145
+ "filename-max-length-unit": { description: "Specify the unit of the maximum length of the filename ('bytes' or 'chars')", type: "string", defaultValue: "bytes" },
146
+ "replace-emojis-in-filename": { description: "Replace emojis in the filename with their unicode text representation", type: "boolean" },
147
+ "output-directory": { description: "Path to where to save files, this path must exist.", type: "string" }
148
+ }, {
149
+ "crawl-links": { description: "Crawl and save pages found via inner links", type: "boolean" },
150
+ "crawl-inner-links-only": { description: "Crawl pages found via inner links only if they are hosted on the same domain", type: "boolean", defaultValue: true },
151
+ "crawl-no-parent": { description: "Crawl pages found via inner links only if their URLs are not parent of the URL to crawl", type: "boolean" },
152
+ "crawl-load-session": { description: "Name of the file of the session to load (previously saved with --crawl-save-session or --crawl-sync-session)", type: "string" },
153
+ "crawl-remove-URL-fragment": { description: "Remove URL fragments found in links", type: "boolean", defaultValue: true },
154
+ "crawl-save-session": { description: "Name of the file where to save the state of the session", type: "string" },
155
+ "crawl-sync-session": { description: "Name of the file where to load and save the state of the session", type: "string" },
156
+ "crawl-max-depth": { description: "Max depth when crawling pages found in internal and external links (0: infinite)", type: "number", defaultValue: 1 },
157
+ "crawl-external-links-max-depth": { description: "Max depth when crawling pages found in external links (0: infinite)", type: "number", defaultValue: 1 },
158
+ "crawl-replace-URLs": { description: "Replace URLs of saved pages with relative paths of saved pages on the filesystem", type: "boolean" },
159
+ "crawl-rewrite-rule": { description: "Rewrite rule used to rewrite URLs of crawled pages", type: "string[]" },
160
+ }, {
161
+ "browser-script": { description: "Path of a script executed in the page (and all the frames) before it is loaded", type: "string[]" },
162
+ "browser-stylesheet": { description: "Path of a stylesheet file inserted into the page (and all the frames) after it is loaded", type: "string[]" },
163
+ "browser-cookie": { description: "Ordered list of cookie parameters separated by a comma (name,value,domain,path,expires,httpOnly,secure,sameSite,url)", type: "string[]" },
164
+ "browser-cookies-file": { description: "Path of the cookies file formatted as a JSON file or a Netscape text file", type: "string" },
165
+ "user-script-enabled": { description: "Enable the event API allowing to execute scripts before the page is saved", type: "boolean", defaultValue: true },
166
+ }, {
167
+ "include-infobar": { description: "Include the infobar", type: "boolean" },
168
+ "infobar-template": { description: "Template used to generate the infobar content (see help page of the extension for more info)", type: "string" },
169
+ "open-infobar": { description: "Keep the infobar open when using --include-infobar", type: "boolean" },
170
+ "infobar-position-absolute": { description: "Position the infobar absolutely (fixed otherwise)", type: "boolean" },
171
+ "infobar-position-top": { description: "Position the infobar at the top of the page", type: "string", defaultValue: "16px" },
172
+ "infobar-position-bottom": { description: "Position the infobar at the bottom of the page", type: "string", defaultValue: "" },
173
+ "infobar-position-right": { description: "Position the infobar at the right of the page", type: "string", defaultValue: "16px" },
174
+ "infobar-position-left": { description: "Position the infobar at the left of the page", type: "string", defaultValue: "" },
175
+ }, {
176
+ "include-BOM": { description: "Include the UTF-8 BOM into the HTML page", type: "boolean" },
177
+ "insert-meta-CSP": { description: "Include a <meta> tag with a CSP to avoid potential requests to internet when viewing a page", type: "boolean", defaultValue: true },
178
+ "remove-saved-date": { description: "Remove saved date metadata in HTML header", type: "boolean" },
179
+ "save-original-URLs": { description: "Save the original URLS in the embedded contents", type: "boolean" },
157
180
  "insert-single-file-comment": { description: "Insert a comment in the HTML header with the URL of the page", type: "boolean", defaultValue: true },
158
- "resolve-links": { description: "Resolve link URLs to absolute URLs", type: "boolean", defaultValue: true },
181
+ "resolve-links": { description: "Resolve link URLs to absolute URLs", type: "boolean", defaultValue: true }
182
+ }, {
183
+ "console-messages-file": { description: "Path of the file where to save the console messages in JSON format", type: "string" },
184
+ "debug-messages-file": { description: "Path of the file where to save the debug messages", type: "string" },
185
+ "errors-file": { description: "Path of the file where to save the error messages", type: "string", alias: "error-file" },
186
+ "errors-traces-disabled": { description: "Remove error stack traces in the error messages", type: "boolean", defaultValue: true, alias: "error-traces-disabled" },
187
+ "dump-content": { description: "Dump the content of the processed page in the console ('true' when running in Docker)", type: "boolean" }
188
+ }, {
189
+ "urls-file": { description: "Path to a text file containing a list of URLs (separated by a newline) to save. You can also pass the options after each URL (e.g. 'https://www.example.com --filename-template={page-title}.html')", type: "string" },
190
+ "max-parallel-workers": { description: "Maximum number of browsers launched in parallel when processing a list of URLs (cf --urls-file)", type: "number", defaultValue: 8 },
159
191
  "settings-file": { description: "Path to a JSON file containing the settings exported from the web extension", type: "string" },
160
- "settings-file-profile": { description: "Name of the profile to use when using --settings-file", type: "string", defaultValue: "default" },
161
- "group-duplicate-stylesheets": { description: "Group duplicate inline stylesheets into a single stylesheet in order to reduce the size of the page", type: "boolean", defaultValue: false }
162
- };
192
+ "settings-file-profile": { description: "Name of the profile to use when using --settings-file", type: "string", defaultValue: "default" }
193
+ }, {
194
+
195
+ "save-raw-page": { description: "Save the original page without interpreting it into the browser", type: "boolean" },
196
+ "output-json": { description: "Output the result as a JSON string containing the page and network info", type: "boolean" },
197
+
198
+ }, {
199
+ "help": { description: "Show help", type: "boolean" },
200
+ "version": { description: "Print the version number and exit.", type: "boolean" },
201
+ }];
163
202
 
164
203
  const { args, exit } = Deno;
165
204
  const options = getOptions();
@@ -174,24 +213,28 @@ function getOptions() {
174
213
  positionals.splice(positionals.indexOf(positional), 1);
175
214
  }
176
215
  });
177
- if ((!positionals.length && !Object.keys(options).length) || positionals.length > 2 || options.help || unknownOptions.length) {
216
+ if (!positionals.length || positionals.length > 2 || options.help || unknownOptions.length) {
178
217
  console.log(USAGE_TEXT + "\n"); // eslint-disable-line no-console
179
218
  console.log("Options:"); // eslint-disable-line no-console
180
- Object.keys(OPTIONS_INFO).forEach(optionName => {
181
- const optionInfo = getOptionInfo(optionName);
182
- let optionType = optionInfo.type;
183
- if (isArray(optionType)) {
184
- optionType = optionType.replace("[]", "*");
185
- }
186
- const optionDescription = optionInfo.description;
187
- const optionDefaultValue = optionInfo.defaultValue === undefined ? "" : `(default: ${JSON.stringify(optionInfo.defaultValue)})`;
188
- console.log(` --${optionName}: ${optionDescription} <${optionType}> ${optionDefaultValue}`); // eslint-disable-line no-console
219
+ OPTIONS_INFO.forEach(category => {
220
+ const categoryName = CATEGORIES[OPTIONS_INFO.indexOf(category)];
221
+ console.log(` * ${categoryName}:`); // eslint-disable-line no-console
222
+ Object.keys(category).forEach(optionName => {
223
+ const optionInfo = getOptionInfo(optionName);
224
+ let optionType = optionInfo.type;
225
+ if (isArray(optionType)) {
226
+ optionType = optionType.replace("[]", "*");
227
+ }
228
+ const optionDescription = optionInfo.description;
229
+ const optionDefaultValue = optionInfo.defaultValue === undefined ? "" : `(default: ${JSON.stringify(optionInfo.defaultValue)})`;
230
+ console.log(` --${optionName}: ${optionDescription} <${optionType}> ${optionDefaultValue}`); // eslint-disable-line no-console
231
+ });
232
+ console.log(""); // eslint-disable-line no-console
189
233
  });
190
234
  if (unknownOptions.length) {
191
- console.log(""); // eslint-disable-line no-console
192
235
  console.log(`Error: Unknown option${unknownOptions.length > 1 ? "s" : ""} ${unknownOptions.join(", ")}`); // eslint-disable-line no-console
236
+ console.log(""); // eslint-disable-line no-console
193
237
  }
194
- console.log(""); // eslint-disable-line no-console
195
238
  exit(0);
196
239
  }
197
240
  if (options.version) {
@@ -257,12 +300,14 @@ function parseArgs(args, setDefaultValues = true) {
257
300
  result.options[optionKey] = optionValue;
258
301
  });
259
302
  if (setDefaultValues) {
260
- Object.keys(OPTIONS_INFO).forEach(optionName => {
261
- const optionInfo = getOptionInfo(optionName);
262
- const optionKey = getOptionKey(optionName, optionInfo);
263
- if (result.options[optionKey] === undefined && optionInfo.defaultValue !== undefined) {
264
- result.options[optionKey] = OPTIONS_INFO[optionName].defaultValue;
265
- }
303
+ OPTIONS_INFO.forEach(categoryOptions => {
304
+ Object.keys(categoryOptions).forEach(optionName => {
305
+ const optionInfo = getOptionInfo(optionName);
306
+ const optionKey = getOptionKey(optionName, optionInfo);
307
+ if (result.options[optionKey] === undefined && optionInfo.defaultValue !== undefined) {
308
+ result.options[optionKey] = categoryOptions[optionName].defaultValue;
309
+ }
310
+ });
266
311
  });
267
312
  }
268
313
  if (result.options.acceptHeaderFont ||
@@ -389,11 +434,15 @@ function parseArg(arg) {
389
434
  }
390
435
 
391
436
  function getOptionInfo(optionName) {
392
- for (const keyName in OPTIONS_INFO) {
393
- if (keyName.toLowerCase() == optionName.toLowerCase() || OPTIONS_INFO[keyName].alias == optionName.toLowerCase()) {
394
- return OPTIONS_INFO[keyName];
395
- }
396
- }
437
+ let result;
438
+ OPTIONS_INFO.forEach(categoryOptions => {
439
+ Object.keys(categoryOptions).forEach(keyName => {
440
+ if (keyName.toLowerCase() == optionName.toLowerCase() || categoryOptions[keyName].alias == optionName.toLowerCase()) {
441
+ result = categoryOptions[keyName];
442
+ }
443
+ });
444
+ });
445
+ return result;
397
446
  }
398
447
 
399
448
  function kebabToCamelCase(optionName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-cli",
3
- "version": "2.0.77",
3
+ "version": "2.0.78",
4
4
  "description": "SingleFile CLI",
5
5
  "author": "Gildas Lormeau",
6
6
  "engines": {
@@ -17,6 +17,6 @@
17
17
  "ws": "^8.18.3"
18
18
  },
19
19
  "devDependencies": {
20
- "@eslint/js": "^9.39.0"
20
+ "@eslint/js": "^9.39.1"
21
21
  }
22
22
  }