single-file-cli 2.0.69 → 2.0.70

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 CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- deno vendor "npm:single-file-core@1.5.36"
3
+ mv package.json package.json.tmp
4
+ mv deno.json deno.json.tmp
5
+ mv deno.lock deno.lock.tmp
6
+ deno install --vendor --quiet "npm:single-file-core@1.5.40"
7
+ mv package.json.tmp package.json
8
+ mv deno.json.tmp deno.json
9
+ mv deno.lock.tmp deno.lock
4
10
 
5
11
  echo "
6
12
  import { build } from 'npm:esbuild';
@@ -82,5 +88,4 @@ const version = JSON.parse(await Deno.readTextFile('./deno.json')).version;
82
88
  await Deno.writeTextFile('lib/version.js', 'export const version = ' + JSON.stringify(version) + ';');
83
89
  " | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock=node_modules/deno.lock.tmp -
84
90
 
85
- rm -rf node_modules
86
- rm -rf vendor
91
+ rm -rf node_modules
package/deno.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@single-file/single-file-cli",
3
- "version": "2.0.69",
3
+ "version": "2.0.70",
4
4
  "description": "SingleFile CLI",
5
- "nodeModulesDir": true,
5
+ "nodeModulesDir": "auto",
6
6
  "exports": {
7
7
  ".": "./single-file-cli-api.js"
8
8
  },
package/lib/cdp-client.js CHANGED
@@ -50,12 +50,19 @@ async function initialize(singleFileOptions) {
50
50
  }
51
51
 
52
52
  async function getPageData(options) {
53
+ const debugMessages = [];
53
54
  let targetInfo;
54
55
  try {
56
+ if (options.debugMessagesFile) {
57
+ debugMessages.push([Date.now(), ["Loading page", EMPTY_PAGE_URL]]);
58
+ }
55
59
  targetInfo = await CDP.createTarget(EMPTY_PAGE_URL);
56
60
  const consoleMessages = [];
57
61
  const { Browser, Security, Page, Emulation, Fetch, Network, Runtime, Debugger, Console } = new CDP(targetInfo);
58
62
  if (options.consoleMessagesFile) {
63
+ if (options.debugMessagesFile) {
64
+ debugMessages.push([Date.now(), ["Enabling console messages"]]);
65
+ }
59
66
  await Console.enable();
60
67
  Console.addEventListener("messageAdded", ({ params }) => {
61
68
  const { message } = params;
@@ -65,13 +72,22 @@ async function getPageData(options) {
65
72
  if (options.browserStartMinimized) {
66
73
  const { windowId, bounds } = await Browser.getWindowForTarget({ targetId: targetInfo.id });
67
74
  if (bounds.windowState !== MINIMIZED_WINDOW_STATE) {
75
+ if (options.debugMessagesFile) {
76
+ debugMessages.push([Date.now(), ["Minimizing window"]]);
77
+ }
68
78
  await Browser.setWindowBounds({ windowId, bounds: { windowState: MINIMIZED_WINDOW_STATE } });
69
79
  }
70
80
  }
71
81
  if (options.browserIgnoreHTTPSErrors !== undefined && options.browserIgnoreHTTPSErrors) {
82
+ if (options.debugMessagesFile) {
83
+ debugMessages.push([Date.now(), ["Ignoring HTTPS errors"]]);
84
+ }
72
85
  await Security.setIgnoreCertificateErrors({ ignore: true });
73
86
  }
74
87
  if (options.browserByPassCSP === undefined || options.browserByPassCSP) {
88
+ if (options.debugMessagesFile) {
89
+ debugMessages.push([Date.now(), ["Bypassing CSP"]]);
90
+ }
75
91
  await Page.setBypassCSP({ enabled: true });
76
92
  }
77
93
  if (options.browserMobileEmulation || options.browserDeviceWidth || options.browserDeviceHeight || options.browserDeviceScaleFactor || options.platform || options.acceptLanguage) {
@@ -91,12 +107,16 @@ async function getPageData(options) {
91
107
  const { result } = await Runtime.evaluate({ expression: "window.devicePixelRatio" });
92
108
  browserDeviceScaleFactor = result.value;
93
109
  }
94
- await Emulation.setDeviceMetricsOverride({
110
+ const deviceMetricsOptions = {
95
111
  mobile: Boolean(options.browserMobileEmulation),
96
112
  width: options.browserDeviceWidth || (options.browserMobileEmulation ? 360 : options.width || browserDeviceWidth),
97
113
  height: options.browserDeviceHeight || (options.browserMobileEmulation ? 800 : options.height || browserDeviceHeight),
98
114
  deviceScaleFactor: options.browserDeviceScaleFactor || (options.browserMobileEmulation ? 2 : browserDeviceScaleFactor)
99
- });
115
+ };
116
+ if (options.debugMessagesFile) {
117
+ debugMessages.push([Date.now(), ["Emulating device metrics", JSON.stringify(deviceMetricsOptions)]]);
118
+ }
119
+ await Emulation.setDeviceMetricsOverride(deviceMetricsOptions);
100
120
  }
101
121
  if (options.browserMobileEmulation || options.platform || options.acceptLanguage) {
102
122
  const { userAgent, product } = await Browser.getVersion();
@@ -109,21 +129,29 @@ async function getPageData(options) {
109
129
  if (options.platform || options.browserMobileEmulation) {
110
130
  agentOptions.platform = options.platform || "Android";
111
131
  }
132
+ if (options.debugMessagesFile) {
133
+ debugMessages.push([Date.now(), ["Emulating user agent", JSON.stringify(agentOptions)]]);
134
+ }
112
135
  await Emulation.setUserAgentOverride(agentOptions);
113
136
  }
114
137
  }
115
138
  const handleAuthRequests = Boolean(options.httpProxyUsername);
116
- const patterns = handleAuthRequests ? [{ requestStage: "Request" }, { requestStage: "Response", resourceType: "Document" }] : [{ requestStage: "Response", resourceType: "Document" }];
139
+ const patterns = handleAuthRequests ? [{ requestStage: "Request" }, { requestStage: "Response" }] : [{ requestStage: "Response" }];
117
140
  await Fetch.enable({ handleAuthRequests, patterns });
118
141
  if (handleAuthRequests) {
119
- Fetch.addEventListener("authRequired", ({ params }) => Fetch.continueWithAuth({
120
- requestId: params.requestId,
121
- authChallengeResponse: {
122
- response: "ProvideCredentials",
123
- username: options.httpProxyUsername,
124
- password: options.httpProxyPassword
142
+ Fetch.addEventListener("authRequired", async ({ params }) => {
143
+ if (options.debugMessagesFile) {
144
+ debugMessages.push([Date.now(), ["Authenticating"]]);
125
145
  }
126
- }));
146
+ await Fetch.continueWithAuth({
147
+ requestId: params.requestId,
148
+ authChallengeResponse: {
149
+ response: "ProvideCredentials",
150
+ username: options.httpProxyUsername,
151
+ password: options.httpProxyPassword
152
+ }
153
+ });
154
+ });
127
155
  }
128
156
  let url = options.url;
129
157
  let alternativeUrl = new URL(url);
@@ -134,12 +162,15 @@ async function getPageData(options) {
134
162
  let httpInfo;
135
163
  Fetch.addEventListener("requestPaused", async ({ params }) => {
136
164
  const { requestId, request, resourceType, responseHeaders, responseStatusCode, responseStatusText } = params;
137
- if (options.outputJson && !httpInfo && (request.url == url || request.url == alternativeUrl) && responseStatusCode !== undefined) {
165
+ if (resourceType == "Document" && (options.outputJson && !httpInfo && (request.url == url || request.url == alternativeUrl) && responseStatusCode !== undefined)) {
138
166
  if (REDIRECT_STATUS_CODES.includes(responseStatusCode)) {
139
167
  const redirect = responseHeaders.find(header => header.name.toLowerCase() == "location").value;
140
168
  if (redirect) {
141
169
  url = new URL(redirect, url).href;
142
170
  }
171
+ if (options.debugMessagesFile) {
172
+ debugMessages.push([Date.now(), ["Redirecting", url]]);
173
+ }
143
174
  } else {
144
175
  httpInfo = {
145
176
  request: {
@@ -156,6 +187,18 @@ async function getPageData(options) {
156
187
  }
157
188
  };
158
189
  }
190
+ } else if (options.blockedURLPatterns && options.blockedURLPatterns.length) {
191
+ const blockedURL = options.blockedURLPatterns.find(blockedURL => new RegExp(blockedURL).test(request.url));
192
+ if (blockedURL) {
193
+ try {
194
+ if (options.debugMessagesFile) {
195
+ debugMessages.push([Date.now(), ["Blocking request", request.url]]);
196
+ }
197
+ await Fetch.failRequest({ requestId, errorReason: "Aborted" });
198
+ } catch {
199
+ // ignored
200
+ }
201
+ }
159
202
  }
160
203
  try {
161
204
  await Fetch.continueRequest({ requestId });
@@ -164,10 +207,16 @@ async function getPageData(options) {
164
207
  }
165
208
  });
166
209
  if (options.httpHeaders && options.httpHeaders.length) {
210
+ if (options.debugMessagesFile) {
211
+ debugMessages.push([Date.now(), ["Setting HTTP headers", JSON.stringify(options.httpHeaders)]]);
212
+ }
167
213
  await Network.setExtraHTTPHeaders({ headers: options.httpHeaders });
168
214
  }
169
215
  if (options.emulateMediaFeatures) {
170
216
  for (const mediaFeature of options.emulateMediaFeatures) {
217
+ if (options.debugMessagesFile) {
218
+ debugMessages.push([Date.now(), ["Emulating media feature", mediaFeature.name, mediaFeature.value]]);
219
+ }
171
220
  await Emulation.setEmulatedMedia({
172
221
  media: mediaFeature.name,
173
222
  features: mediaFeature.value.split(",").map(feature => feature.trim())
@@ -175,6 +224,9 @@ async function getPageData(options) {
175
224
  }
176
225
  }
177
226
  if (options.browserCookies && options.browserCookies.length) {
227
+ if (options.debugMessagesFile) {
228
+ debugMessages.push([Date.now(), ["Setting cookies", JSON.stringify(options.browserCookies)]]);
229
+ }
178
230
  await Network.setCookies({ cookies: options.browserCookies });
179
231
  }
180
232
  await Browser.setDownloadBehavior({ behavior: "deny" });
@@ -188,7 +240,7 @@ async function getPageData(options) {
188
240
  worldName: SINGLE_FILE_WORLD_NAME
189
241
  });
190
242
  const [contextId] = await Promise.all([
191
- loadPage({ Page, Runtime }, options),
243
+ loadPage({ Page, Runtime }, options, debugMessages),
192
244
  options.browserDebug ? waitForDebuggerReady({ Debugger }) : Promise.resolve()
193
245
  ]);
194
246
  await Runtime.addBinding({ name: SET_PAGE_DATA_FUNCTION_NAME, executionContextId: contextId });
@@ -196,6 +248,9 @@ async function getPageData(options) {
196
248
  await Runtime.addBinding({ name: CAPTURE_SCREENSHOT_FUNCTION_NAME, executionContextId: contextId });
197
249
  Runtime.addEventListener("bindingCalled", async ({ params }) => {
198
250
  if (params.name === CAPTURE_SCREENSHOT_FUNCTION_NAME) {
251
+ if (options.debugMessagesFile) {
252
+ debugMessages.push([Date.now(), ["Capturing screenshot"]]);
253
+ }
199
254
  try {
200
255
  let screenshotOptions = { captureBeyondViewport: true };
201
256
  if (options.embedScreenshotOptions) {
@@ -218,6 +273,9 @@ async function getPageData(options) {
218
273
  await Runtime.addBinding({ name: PRINT_TO_PDF_FUNCTION_NAME, executionContextId: contextId });
219
274
  Runtime.addEventListener("bindingCalled", async ({ params }) => {
220
275
  if (params.name === PRINT_TO_PDF_FUNCTION_NAME) {
276
+ if (options.debugMessagesFile) {
277
+ debugMessages.push([Date.now(), ["Printing to PDF", options.embedPdfOptions || ""]]);
278
+ }
221
279
  let pdfOptions = {};
222
280
  if (options.embedPdfOptions) {
223
281
  try {
@@ -243,6 +301,9 @@ async function getPageData(options) {
243
301
  if (payload.length) {
244
302
  pageDataResponse += payload;
245
303
  } else {
304
+ if (options.debugMessagesFile) {
305
+ debugMessages.push([Date.now(), ["Setting page data"]]);
306
+ }
246
307
  const result = JSON.parse(pageDataResponse);
247
308
  if (result.content instanceof Array) {
248
309
  result.content = new Uint8Array(result.content);
@@ -253,8 +314,14 @@ async function getPageData(options) {
253
314
  });
254
315
  });
255
316
  if (options.browserWaitDelay) {
317
+ if (options.debugMessagesFile) {
318
+ debugMessages.push([Date.now(), [`Waiting ${options.browserWaitDelay} ms`]]);
319
+ }
256
320
  await new Promise(resolve => setTimeout(resolve, options.browserWaitDelay));
257
321
  }
322
+ if (options.debugMessagesFile) {
323
+ debugMessages.push([Date.now(), ["Capturing page"]]);
324
+ }
258
325
  const { result } = await Runtime.evaluate({
259
326
  expression: `(${getPageDataScriptSource.toString()})(${JSON.stringify(options)},${JSON.stringify([SET_SCREENSHOT_FUNCTION_NAME, SET_PDF_FUNCTION_NAME, SET_PAGE_DATA_FUNCTION_NAME, CAPTURE_SCREENSHOT_FUNCTION_NAME, PRINT_TO_PDF_FUNCTION_NAME])})`,
260
327
  awaitPromise: true,
@@ -270,19 +337,35 @@ async function getPageData(options) {
270
337
  await Console.disable();
271
338
  pageData.consoleMessages = consoleMessages;
272
339
  }
340
+ if (options.debugMessagesFile) {
341
+ pageData.debugMessages = debugMessages;
342
+ debugMessages.push([Date.now(), ["Returning page data"]]);
343
+ }
273
344
  Object.assign(pageData, httpInfo);
274
345
  return pageData;
275
346
  } catch (error) {
276
347
  if (error.code === LOAD_TIMEOUT_ERROR && options.browserWaitUntilFallback && options.browserWaitUntil) {
277
348
  const browserWaitUntil = NETWORK_STATES[(NETWORK_STATES.indexOf(options.browserWaitUntil) + 1)];
278
349
  if (browserWaitUntil) {
350
+ if (options.debugMessagesFile) {
351
+ debugMessages.push([Date.now(), ["Retrying with waitUntil", browserWaitUntil]]);
352
+ }
279
353
  options.browserWaitUntil = browserWaitUntil;
280
354
  await closeTarget();
281
355
  return await getPageData(options);
282
356
  }
283
357
  }
358
+ if (options.consoleMessagesFile) {
359
+ error.consoleMessages = consoleMessages;
360
+ }
361
+ if (options.debugMessagesFile) {
362
+ error.debugMessages = debugMessages;
363
+ }
284
364
  throw error;
285
365
  } finally {
366
+ if (options.debugMessagesFile) {
367
+ debugMessages.push([Date.now(), ["Closing page"]]);
368
+ }
286
369
  await closeTarget();
287
370
  }
288
371
 
@@ -335,14 +418,17 @@ function getPageDataScriptSource(options, [SET_SCREENSHOT_FUNCTION_NAME, SET_PDF
335
418
  });
336
419
  }
337
420
 
338
- async function loadPage({ Page, Runtime }, options) {
421
+ async function loadPage({ Page, Runtime }, options, debugMessages) {
339
422
  await Runtime.enable();
340
423
  await Page.enable();
341
424
  const loadTimeoutAbortController = new AbortController();
342
425
  const loadTimeoutAbortSignal = loadTimeoutAbortController.signal;
343
426
  try {
427
+ if (options.debugMessagesFile) {
428
+ debugMessages.push([Date.now(), ["Loading page", options.url]]);
429
+ }
344
430
  const [contextId] = await Promise.race([
345
- Promise.all([getTopFrameContextId({ Page, Runtime }, options), Page.navigate({ url: options.url })]),
431
+ Promise.all([getTopFrameContextId({ Page, Runtime }, options, debugMessages), Page.navigate({ url: options.url })]),
346
432
  waitForLoadTimeout(loadTimeoutAbortSignal, options.browserLoadMaxTime)
347
433
  ]);
348
434
  return contextId;
@@ -355,13 +441,16 @@ async function loadPage({ Page, Runtime }, options) {
355
441
  }
356
442
  }
357
443
 
358
- async function getTopFrameContextId({ Page, Runtime }, options) {
444
+ async function getTopFrameContextId({ Page, Runtime }, options, debugMessages) {
359
445
  const CONTEXT_CREATED_EVENT = "executionContextCreated";
360
446
  const contextIds = [];
361
447
  let topFrameId;
362
448
  try {
363
449
  Runtime.addEventListener(CONTEXT_CREATED_EVENT, onExecutionContextCreated);
364
450
  await waitForPageReady({ Page }, options);
451
+ if (options.debugMessagesFile) {
452
+ debugMessages.push([Date.now(), ["Getting execution context"]]);
453
+ }
365
454
  const contextId = await getContextId();
366
455
  if (contextId === undefined) {
367
456
  throw new Error("Execution context not found");
@@ -392,12 +481,23 @@ async function getTopFrameContextId({ Page, Runtime }, options) {
392
481
 
393
482
  function onLifecycleEvent({ params }) {
394
483
  const { frameId, name } = params;
395
- if (frameId === topFrameId && name === options.browserWaitUntil || (resolveCallbackTimeout && NETWORK_STATES.indexOf(name) < NETWORK_STATES.indexOf(options.browserWaitUntil))) {
396
- clearTimeout(resolveCallbackTimeout);
397
- setTimeout(() => {
398
- removeListeners();
399
- resolve();
400
- }, options.browserWaitUntilDelay);
484
+ if (frameId === topFrameId) {
485
+ if (options.debugMessagesFile) {
486
+ debugMessages.push([Date.now(), ["Detecting lifecycle event", name]]);
487
+ }
488
+ if (name === options.browserWaitUntil || (resolveCallbackTimeout && NETWORK_STATES.indexOf(name) < NETWORK_STATES.indexOf(options.browserWaitUntil))) {
489
+ clearTimeout(resolveCallbackTimeout);
490
+ if (options.debugMessagesFile) {
491
+ debugMessages.push([Date.now(), [`Waiting ${options.browserWaitUntilDelay} ms`]]);
492
+ }
493
+ setTimeout(() => {
494
+ if (options.debugMessagesFile) {
495
+ debugMessages.push([Date.now(), ["Detecting page ready"]]);
496
+ }
497
+ removeListeners();
498
+ resolve();
499
+ }, options.browserWaitUntilDelay);
500
+ }
401
501
  }
402
502
  }
403
503
 
@@ -409,6 +509,9 @@ async function getTopFrameContextId({ Page, Runtime }, options) {
409
509
  removeListeners();
410
510
  reject(new Error("Unreachable URL: " + frame.unreachableUrl));
411
511
  } else {
512
+ if (options.debugMessagesFile) {
513
+ debugMessages.push([Date.now(), ["Detecting top frame ID"]]);
514
+ }
412
515
  topFrameId = frame.id;
413
516
  }
414
517
  }
package/lib/constants.js CHANGED
@@ -51,7 +51,9 @@ const BROWSER_ARGS = [
51
51
  "--enable-use-zoom-for-dsf=false",
52
52
  "--no-sandbox",
53
53
  "--no-startup-window",
54
- "--bwsi"
54
+ "--bwsi",
55
+ "--mute-audio",
56
+ "--deny-permission-prompts"
55
57
  ];
56
58
  const BROWSER_PATHS = {
57
59
  linux: [