single-file-cli 2.0.67 → 2.0.69
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/README.MD +1 -1
- package/build.sh +1 -1
- package/deno.json +1 -1
- package/lib/browser.js +2 -2
- package/lib/cdp-client.js +22 -10
- package/lib/deno-polyfill.js +3 -1
- package/lib/single-file-bundle.js +1 -1
- package/lib/single-file-script.js +1 -1
- package/lib/version.js +1 -1
- package/options.js +70 -41
- package/package.json +1 -1
- package/single-file-cli-api.js +22 -11
- package/single-file-launcher.js +56 -3
|
@@ -41,7 +41,7 @@ function initSingleFile() {
|
|
|
41
41
|
xhrRequest.onreadystatechange = () => {
|
|
42
42
|
if (xhrRequest.readyState == XMLHttpRequest.DONE) {
|
|
43
43
|
resolve({
|
|
44
|
-
arrayBuffer:
|
|
44
|
+
arrayBuffer: () => xhrRequest.response || new ArrayBuffer(),
|
|
45
45
|
headers: { get: headerName => xhrRequest.getResponseHeader(headerName) },
|
|
46
46
|
status: xhrRequest.status
|
|
47
47
|
});
|
package/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "2.0.
|
|
1
|
+
export const version = "2.0.69";
|
package/options.js
CHANGED
|
@@ -50,6 +50,7 @@ const OPTIONS_INFO = {
|
|
|
50
50
|
"browser-load-max-time": { description: "Maximum delay of time to wait for page loading in ms", type: "number", defaultValue: 60000 },
|
|
51
51
|
"browser-wait-delay": { description: "Time to wait before capturing the page in ms", type: "number" },
|
|
52
52
|
"browser-wait-until": { description: "When to consider the page is loaded (InteractiveTime, networkIdle, networkAlmostIdle, load, domContentLoaded)", type: "string", defaultValue: "networkIdle" },
|
|
53
|
+
"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 },
|
|
53
54
|
"browser-wait-until-fallback": { description: "Retry with the next value of --browser-wait-until when a timeout error is thrown", type: "boolean", defaultValue: true },
|
|
54
55
|
"browser-debug": { description: "Enable debug mode", type: "boolean" },
|
|
55
56
|
"browser-script": { description: "Path of a script executed in the page (and all the frames) before it is loaded", type: "string[]" },
|
|
@@ -121,7 +122,7 @@ const OPTIONS_INFO = {
|
|
|
121
122
|
"remove-alternative-images": { description: "Remove images for alternative sizes of screen", type: "boolean", defaultValue: true },
|
|
122
123
|
"save-original-URLs": { description: "Save the original URLS in the embedded contents", type: "boolean" },
|
|
123
124
|
"save-raw-page": { description: "Save the original page without interpreting it into the browser", type: "boolean" },
|
|
124
|
-
"urls-file": { description: "Path to a text file containing a list of URLs (separated by a newline) to save", type: "string" },
|
|
125
|
+
"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" },
|
|
125
126
|
"user-agent": { description: "User-agent of the browser", type: "string" },
|
|
126
127
|
"accept-language": { description: "Accept language of the browser", type: "string" },
|
|
127
128
|
"platform": { description: "Platform of the browser (default value is \"Android\" when using --browser-mobile-emulation)", type: "string" },
|
|
@@ -149,10 +150,10 @@ const OPTIONS_INFO = {
|
|
|
149
150
|
};
|
|
150
151
|
|
|
151
152
|
const { args, exit } = Deno;
|
|
152
|
-
const options =
|
|
153
|
-
export
|
|
153
|
+
const options = getOptions();
|
|
154
|
+
export { options, parseArgs };
|
|
154
155
|
|
|
155
|
-
|
|
156
|
+
function getOptions() {
|
|
156
157
|
const { positionals, options } = parseArgs(Array.from(args));
|
|
157
158
|
const unknownOptions = [];
|
|
158
159
|
positionals.forEach(positional => {
|
|
@@ -185,43 +186,6 @@ async function getOptions() {
|
|
|
185
186
|
console.log(version); // eslint-disable-line no-console
|
|
186
187
|
exit(0);
|
|
187
188
|
}
|
|
188
|
-
Object.keys(OPTIONS_INFO).forEach(optionName => {
|
|
189
|
-
const optionInfo = getOptionInfo(optionName);
|
|
190
|
-
const optionKey = getOptionKey(optionName, optionInfo);
|
|
191
|
-
if (options[optionKey] === undefined && optionInfo.defaultValue !== undefined) {
|
|
192
|
-
options[optionKey] = OPTIONS_INFO[optionName].defaultValue;
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
options.acceptHeaders = {
|
|
196
|
-
font: options.acceptHeaderFont,
|
|
197
|
-
image: options.acceptHeaderImage,
|
|
198
|
-
stylesheet: options.acceptHeaderStylesheet,
|
|
199
|
-
script: options.acceptHeaderScript,
|
|
200
|
-
document: options.acceptHeaderDocument
|
|
201
|
-
};
|
|
202
|
-
if (options.browserArgs) {
|
|
203
|
-
const browserArguments = options.browserArguments || [];
|
|
204
|
-
browserArguments.push(...JSON.parse(options.browserArgs));
|
|
205
|
-
options.browserArgs = browserArguments;
|
|
206
|
-
delete options.browserArguments;
|
|
207
|
-
}
|
|
208
|
-
if (options.browserArguments) {
|
|
209
|
-
options.browserArgs = options.browserArguments;
|
|
210
|
-
delete options.browserArguments;
|
|
211
|
-
}
|
|
212
|
-
if (options.errorFile) {
|
|
213
|
-
options.errorsFile = options.errorFile;
|
|
214
|
-
delete options.errorFile;
|
|
215
|
-
}
|
|
216
|
-
if (options.errorTracesDisabled) {
|
|
217
|
-
options.errorsTracesDisabled = options.errorTracesDisabled;
|
|
218
|
-
delete options.errorTracesDisabled;
|
|
219
|
-
}
|
|
220
|
-
delete options.acceptHeaderFont;
|
|
221
|
-
delete options.acceptHeaderImage;
|
|
222
|
-
delete options.acceptHeaderStylesheet;
|
|
223
|
-
delete options.acceptHeaderScript;
|
|
224
|
-
delete options.acceptHeaderDocument;
|
|
225
189
|
return { ...options, url: positionals[0], output: positionals[1] };
|
|
226
190
|
}
|
|
227
191
|
|
|
@@ -280,6 +244,71 @@ function parseArgs(args) {
|
|
|
280
244
|
}
|
|
281
245
|
result.options[optionKey] = optionValue;
|
|
282
246
|
});
|
|
247
|
+
Object.keys(OPTIONS_INFO).forEach(optionName => {
|
|
248
|
+
const optionInfo = getOptionInfo(optionName);
|
|
249
|
+
const optionKey = getOptionKey(optionName, optionInfo);
|
|
250
|
+
if (result.options[optionKey] === undefined && optionInfo.defaultValue !== undefined) {
|
|
251
|
+
result.options[optionKey] = OPTIONS_INFO[optionName].defaultValue;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
result.options.acceptHeaders = {
|
|
255
|
+
font: result.options.acceptHeaderFont,
|
|
256
|
+
image: result.options.acceptHeaderImage,
|
|
257
|
+
stylesheet: result.options.acceptHeaderStylesheet,
|
|
258
|
+
script: result.options.acceptHeaderScript,
|
|
259
|
+
document: result.options.acceptHeaderDocument
|
|
260
|
+
};
|
|
261
|
+
if (result.options.browserArgs) {
|
|
262
|
+
const browserArguments = result.options.browserArguments || [];
|
|
263
|
+
browserArguments.push(...JSON.parse(result.options.browserArgs));
|
|
264
|
+
result.options.browserArgs = browserArguments;
|
|
265
|
+
delete result.options.browserArguments;
|
|
266
|
+
}
|
|
267
|
+
if (result.options.browserArguments) {
|
|
268
|
+
result.options.browserArgs = result.options.browserArguments;
|
|
269
|
+
delete result.options.browserArguments;
|
|
270
|
+
}
|
|
271
|
+
if (result.options.errorFile) {
|
|
272
|
+
result.options.errorsFile = result.options.errorFile;
|
|
273
|
+
delete result.options.errorFile;
|
|
274
|
+
}
|
|
275
|
+
if (result.options.errorTracesDisabled) {
|
|
276
|
+
result.options.errorsTracesDisabled = result.options.errorTracesDisabled;
|
|
277
|
+
delete result.options.errorTracesDisabled;
|
|
278
|
+
}
|
|
279
|
+
if (result.options.crawlReplaceUrls) {
|
|
280
|
+
result.options.crawlReplaceURLs = result.options.crawlReplaceUrls;
|
|
281
|
+
delete result.options.crawlReplaceUrls;
|
|
282
|
+
}
|
|
283
|
+
if (result.options.saveOriginalUrls) {
|
|
284
|
+
result.options.saveOriginalURLs = result.options.saveOriginalUrls;
|
|
285
|
+
delete result.options.saveOriginalUrls;
|
|
286
|
+
}
|
|
287
|
+
if (result.options.browserRemoteDebuggingUrl) {
|
|
288
|
+
result.options.browserRemoteDebuggingURL = result.options.browserRemoteDebuggingUrl;
|
|
289
|
+
delete result.options.browserRemoteDebuggingUrl;
|
|
290
|
+
}
|
|
291
|
+
if (result.options.crawlRemoveUrlFragment) {
|
|
292
|
+
result.options.crawlRemoveURLFragment = result.options.crawlRemoveUrlFragment;
|
|
293
|
+
delete result.options.crawlRemoveUrlFragment;
|
|
294
|
+
}
|
|
295
|
+
if (result.options.compressCss) {
|
|
296
|
+
result.options.compressCSS = result.options.compressCss;
|
|
297
|
+
delete result.options.compressCss;
|
|
298
|
+
}
|
|
299
|
+
if (result.options.compressHtml) {
|
|
300
|
+
result.options.compressHTML = result.options.compressHtml;
|
|
301
|
+
delete result.options.compressHtml;
|
|
302
|
+
}
|
|
303
|
+
if (result.options.insertMetaCsp) {
|
|
304
|
+
result.options.insertMetaCSP = result.options.insertMetaCsp;
|
|
305
|
+
delete result.options.insertMetaCsp;
|
|
306
|
+
}
|
|
307
|
+
delete result.options.acceptHeaderFont;
|
|
308
|
+
delete result.options.acceptHeaderImage;
|
|
309
|
+
delete result.options.acceptHeaderStylesheet;
|
|
310
|
+
delete result.options.acceptHeaderScript;
|
|
311
|
+
delete result.options.acceptHeaderDocument;
|
|
283
312
|
return result;
|
|
284
313
|
}
|
|
285
314
|
|
package/package.json
CHANGED
package/single-file-cli-api.js
CHANGED
|
@@ -101,7 +101,17 @@ async function initialize(options) {
|
|
|
101
101
|
async function capture(urls, options) {
|
|
102
102
|
let newTasks;
|
|
103
103
|
const taskUrls = tasks.map(task => task.url);
|
|
104
|
-
newTasks = await Promise.all(urls.map(
|
|
104
|
+
newTasks = await Promise.all(urls.map(value => {
|
|
105
|
+
let url, taskOptions;
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
url = value[0];
|
|
108
|
+
taskOptions = Object.assign({}, options, value[1]);
|
|
109
|
+
} else {
|
|
110
|
+
url = value;
|
|
111
|
+
taskOptions = options;
|
|
112
|
+
}
|
|
113
|
+
return createTask(url, taskOptions);
|
|
114
|
+
}));
|
|
105
115
|
newTasks = newTasks.filter(task => task && !taskUrls.includes(task.url));
|
|
106
116
|
if (newTasks.length) {
|
|
107
117
|
tasks = tasks.concat(newTasks);
|
|
@@ -127,7 +137,7 @@ async function finish(options) {
|
|
|
127
137
|
}
|
|
128
138
|
});
|
|
129
139
|
await writeTextFile(task.filename, pageContent);
|
|
130
|
-
} catch
|
|
140
|
+
} catch {
|
|
131
141
|
// ignored
|
|
132
142
|
}
|
|
133
143
|
}
|
|
@@ -137,7 +147,7 @@ async function finish(options) {
|
|
|
137
147
|
}
|
|
138
148
|
}
|
|
139
149
|
|
|
140
|
-
|
|
150
|
+
function runTasks() {
|
|
141
151
|
const availableTasks = tasks.filter(task => !task.status).length;
|
|
142
152
|
const processingTasks = tasks.filter(task => task.status == STATE_PROCESSING).length;
|
|
143
153
|
const promisesTasks = [];
|
|
@@ -151,7 +161,7 @@ async function runNextTask() {
|
|
|
151
161
|
const task = tasks.find(task => !task.status);
|
|
152
162
|
if (task) {
|
|
153
163
|
const options = task.options;
|
|
154
|
-
|
|
164
|
+
const taskOptions = JSON.parse(JSON.stringify(options));
|
|
155
165
|
taskOptions.url = task.url;
|
|
156
166
|
task.status = STATE_PROCESSING;
|
|
157
167
|
await saveTasks();
|
|
@@ -162,10 +172,11 @@ async function runNextTask() {
|
|
|
162
172
|
task.filename = pageData.filename;
|
|
163
173
|
if (options.crawlLinks && testMaxDepth(task)) {
|
|
164
174
|
const urls = pageData.links;
|
|
165
|
-
let newTasks = await Promise.all(urls.map(url => createTask(url, options, task,
|
|
175
|
+
let newTasks = await Promise.all(urls.map(url => createTask(url, options, task, task.rootTaskURL || task.url)));
|
|
166
176
|
newTasks = newTasks.filter(task => task &&
|
|
167
177
|
testMaxDepth(task) &&
|
|
168
178
|
!tasks.find(otherTask => otherTask.url == task.url) &&
|
|
179
|
+
!newTasks.find(otherTask => otherTask != task && otherTask.url == task.url) &&
|
|
169
180
|
(!options.crawlInnerLinksOnly || task.isInnerLink) &&
|
|
170
181
|
(!options.crawlNoParent || (task.isChild || !task.isInnerLink)));
|
|
171
182
|
tasks.splice(tasks.length, 0, ...newTasks);
|
|
@@ -182,7 +193,7 @@ function testMaxDepth(task) {
|
|
|
182
193
|
(options.crawlExternalLinksMaxDepth == 0 || task.externalLinkDepth < options.crawlExternalLinksMaxDepth);
|
|
183
194
|
}
|
|
184
195
|
|
|
185
|
-
async function createTask(url, options, parentTask,
|
|
196
|
+
async function createTask(url, options, parentTask, rootTaskURL) {
|
|
186
197
|
url = parentTask ? rewriteURL(url, options.crawlRemoveURLFragment, options.crawlRewriteRules) : url;
|
|
187
198
|
if (url) {
|
|
188
199
|
if (!VALID_URL_TEST.test(url)) {
|
|
@@ -195,15 +206,15 @@ async function createTask(url, options, parentTask, rootTask) {
|
|
|
195
206
|
throw new Error("Invalid URL or file path: " + url, { cause: error });
|
|
196
207
|
}
|
|
197
208
|
}
|
|
198
|
-
const isInnerLink =
|
|
199
|
-
const rootBaseURIMatch =
|
|
209
|
+
const isInnerLink = rootTaskURL && url.startsWith(getHostURL(rootTaskURL));
|
|
210
|
+
const rootBaseURIMatch = rootTaskURL && rootTaskURL.match(/(.*?)[^/]*$/);
|
|
200
211
|
const isChild = isInnerLink && rootBaseURIMatch && rootBaseURIMatch[1] && url.startsWith(rootBaseURIMatch[1]);
|
|
201
212
|
return {
|
|
202
213
|
url,
|
|
203
214
|
isInnerLink,
|
|
204
215
|
isChild,
|
|
205
216
|
originalUrl: url,
|
|
206
|
-
|
|
217
|
+
rootTaskURL,
|
|
207
218
|
depth: parentTask ? parentTask.depth + 1 : 0,
|
|
208
219
|
externalLinkDepth: isInnerLink ? -1 : parentTask ? parentTask.externalLinkDepth + 1 : -1,
|
|
209
220
|
options
|
|
@@ -282,7 +293,7 @@ async function capturePage(options) {
|
|
|
282
293
|
if (options.outputJson) {
|
|
283
294
|
filename += filename.endsWith(".json") ? "" : ".json";
|
|
284
295
|
}
|
|
285
|
-
const directoryName =
|
|
296
|
+
const directoryName = path.dirname(filename);
|
|
286
297
|
if (directoryName !== ".") {
|
|
287
298
|
await mkdir(directoryName, { recursive: true });
|
|
288
299
|
}
|
|
@@ -338,7 +349,7 @@ async function getFilename(filename, options, index = 1) {
|
|
|
338
349
|
if (options.filenameConflictAction != "skip") {
|
|
339
350
|
return getFilename(filename, options, index + 1);
|
|
340
351
|
}
|
|
341
|
-
} catch
|
|
352
|
+
} catch {
|
|
342
353
|
return newFilename;
|
|
343
354
|
}
|
|
344
355
|
}
|
package/single-file-launcher.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { initialize } from "./single-file-cli-api.js";
|
|
25
25
|
import { closeBrowser } from "./lib/browser.js";
|
|
26
26
|
import { Deno } from "./lib/deno-polyfill.js";
|
|
27
|
-
import options from "./options.js";
|
|
27
|
+
import { options, parseArgs } from "./options.js";
|
|
28
28
|
|
|
29
29
|
const { readTextFile, readFile, exit, addSignalListener } = Deno;
|
|
30
30
|
|
|
@@ -54,7 +54,7 @@ async function run() {
|
|
|
54
54
|
delete options.settingsFile;
|
|
55
55
|
}
|
|
56
56
|
if (options.urlsFile) {
|
|
57
|
-
urls =
|
|
57
|
+
urls = await getUrlsFile(options.urlsFile);
|
|
58
58
|
} else {
|
|
59
59
|
urls = [options.url];
|
|
60
60
|
}
|
|
@@ -80,7 +80,7 @@ async function run() {
|
|
|
80
80
|
const cookiesContent = await readTextFile(options.browserCookiesFile);
|
|
81
81
|
try {
|
|
82
82
|
options.browserCookies = JSON.parse(cookiesContent);
|
|
83
|
-
} catch
|
|
83
|
+
} catch {
|
|
84
84
|
options.browserCookies = parseCookies(cookiesContent);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -137,3 +137,56 @@ async function closeBrowserAndExit(code) {
|
|
|
137
137
|
await closeBrowser();
|
|
138
138
|
exit(code);
|
|
139
139
|
}
|
|
140
|
+
|
|
141
|
+
async function getUrlsFile(urlsFile) {
|
|
142
|
+
let urls = (await readTextFile(urlsFile)).split("\n");
|
|
143
|
+
urls = urls.map(value => {
|
|
144
|
+
value = value.trim();
|
|
145
|
+
let optionPosition = value.indexOf(" --");
|
|
146
|
+
if (optionPosition < 0) {
|
|
147
|
+
optionPosition = value.indexOf("\t--");
|
|
148
|
+
}
|
|
149
|
+
if (optionPosition > 0) {
|
|
150
|
+
const url = value.substring(0, optionPosition).trim();
|
|
151
|
+
const argsString = value.substring(optionPosition + 1).trim();
|
|
152
|
+
const args = [];
|
|
153
|
+
let previousCharacter, previousPreviousCharacter, lastQuoteCharacter;
|
|
154
|
+
let lastCharIndex = 0;
|
|
155
|
+
for (let currentCharIndex = 0; currentCharIndex < argsString.length; currentCharIndex++) {
|
|
156
|
+
const character = argsString[currentCharIndex];
|
|
157
|
+
if (character == lastQuoteCharacter && (previousCharacter != "\\" || previousPreviousCharacter == "\\")) {
|
|
158
|
+
args.push(argsString.substring(lastCharIndex, currentCharIndex));
|
|
159
|
+
lastQuoteCharacter = null;
|
|
160
|
+
lastCharIndex = currentCharIndex + 1;
|
|
161
|
+
} else if (!lastQuoteCharacter) {
|
|
162
|
+
if (character == "'" || character == "\"") {
|
|
163
|
+
lastQuoteCharacter = argsString[currentCharIndex];
|
|
164
|
+
lastCharIndex = currentCharIndex + 1;
|
|
165
|
+
} else {
|
|
166
|
+
const isSpaceCharacter = character == " " || character == "\t";
|
|
167
|
+
if (isSpaceCharacter || character == "=") {
|
|
168
|
+
if (isSpaceCharacter && (currentCharIndex == lastCharIndex + 1)) {
|
|
169
|
+
lastCharIndex++;
|
|
170
|
+
} else if (lastCharIndex < currentCharIndex) {
|
|
171
|
+
args.push(argsString.substring(lastCharIndex, currentCharIndex));
|
|
172
|
+
lastCharIndex = currentCharIndex + 1;
|
|
173
|
+
} else {
|
|
174
|
+
lastCharIndex = currentCharIndex + 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
previousPreviousCharacter = previousCharacter;
|
|
180
|
+
previousCharacter = character;
|
|
181
|
+
}
|
|
182
|
+
if (lastCharIndex < argsString.length) {
|
|
183
|
+
args.push(argsString.substring(lastCharIndex).trim());
|
|
184
|
+
}
|
|
185
|
+
const { options } = parseArgs(args);
|
|
186
|
+
return [url, options];
|
|
187
|
+
} else {
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return urls;
|
|
192
|
+
}
|