single-file-cli 2.6.3 → 2.7.0

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  set -e
4
4
 
5
- CORE_PACKAGE="npm:single-file-core@1.5.118"
5
+ CORE_PACKAGE="npm:single-file-core@1.5.120"
6
6
  ESBUILD_PACKAGE="npm:esbuild@0.27.7"
7
7
 
8
8
  project_dir=$(pwd)
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@single-file/single-file-cli",
3
- "version": "2.6.3",
3
+ "version": "2.7.0",
4
4
  "description": "SingleFile CLI",
5
5
  "exports": {
6
6
  ".": "./single-file-cli-api.js"
package/lib/browser.js CHANGED
@@ -59,7 +59,23 @@ const PROFILE_INCOMPATIBLE_ARGS = [
59
59
  "--deny-permission-prompts"
60
60
  ];
61
61
 
62
- const { build, makeTempDir, readDir, copyFile, mkdir, Command, errors, remove } = Deno;
62
+ const { build, makeTempDir, readDir, copyFile, mkdir, Command, errors, remove, stat } = Deno;
63
+ const WATCHDOG_SHELL = "/bin/sh";
64
+ const WATCHDOG_SCRIPT = [
65
+ "trap 'kill -TERM $browser 2>/dev/null' TERM INT",
66
+ "profile=$1",
67
+ "shift",
68
+ "\"$0\" \"$@\" &",
69
+ "browser=$!",
70
+ "parent=$PPID",
71
+ "while kill -0 $parent 2>/dev/null && kill -0 $browser 2>/dev/null; do sleep 0.25; done",
72
+ "if ! kill -0 $parent 2>/dev/null; then",
73
+ "kill -TERM $browser 2>/dev/null",
74
+ "wait $browser 2>/dev/null",
75
+ "if [ -n \"$profile\" ]; then rm -rf \"$profile\"; fi",
76
+ "fi",
77
+ "wait $browser 2>/dev/null"
78
+ ].join("\n");
63
79
  const { join } = path;
64
80
  let child, profilePath, childExited, keepProfilePath, browserDebugPort;
65
81
  export { launchBrowser, createBrowserProfile, getBrowserOptions, copyProfile, pruneProfile, closeBrowser, browserExited };
@@ -139,9 +155,8 @@ async function launchBrowser(options = {}, indexPath = 0) {
139
155
  if (options.startUrl) {
140
156
  args.push(options.startUrl);
141
157
  }
142
- const command = new Command(executablePath, { args, stdout: NULL_STD_CONFIG, stderr: NULL_STD_CONFIG });
143
158
  try {
144
- child = await command.spawn();
159
+ child = await spawnBrowser(executablePath, args, keepProfilePath ? "" : profilePath);
145
160
  } catch (error) {
146
161
  if (!keepProfilePath) {
147
162
  await remove(profilePath, { recursive: true }).catch(() => { });
@@ -173,6 +188,18 @@ async function launchBrowser(options = {}, indexPath = 0) {
173
188
  return debugPort;
174
189
  }
175
190
 
191
+ async function spawnBrowser(executablePath, args, temporaryProfilePath) {
192
+ if (build.os == "windows") {
193
+ return new Command(executablePath, { args, stdout: NULL_STD_CONFIG, stderr: NULL_STD_CONFIG }).spawn();
194
+ }
195
+ try {
196
+ await stat(executablePath);
197
+ } catch {
198
+ throw new errors.NotFound(executablePath);
199
+ }
200
+ return new Command(WATCHDOG_SHELL, { args: ["-c", WATCHDOG_SCRIPT, executablePath, temporaryProfilePath, ...args], stdout: NULL_STD_CONFIG, stderr: NULL_STD_CONFIG }).spawn();
201
+ }
202
+
176
203
  async function waitUntilReady(debugPort) {
177
204
  const timeoutTime = Date.now() + READY_TIMEOUT;
178
205
  while (!childExited && Date.now() < timeoutTime) {
package/lib/cdp-client.js CHANGED
@@ -122,9 +122,6 @@ async function getPageData(options) {
122
122
  await disableCdpDomains(cdp, pageContext);
123
123
  return await finalizePageData(pageDataPromise, pageContext);
124
124
  } catch (error) {
125
- if (shouldRetryWithFallback(error)) {
126
- return await retryWithFallback();
127
- }
128
125
  if (await shouldRelaunchBrowser()) {
129
126
  return await relaunchBrowserAndRetry();
130
127
  }
@@ -137,21 +134,6 @@ async function getPageData(options) {
137
134
  logData(["Finishing"], pageContext);
138
135
  }
139
136
 
140
- function shouldRetryWithFallback(error) {
141
- return error.code === LOAD_TIMEOUT_ERROR &&
142
- options.browserWaitUntilFallback &&
143
- options.browserWaitUntil &&
144
- NETWORK_STATES.indexOf(options.browserWaitUntil) < NETWORK_STATES.length - 1;
145
- }
146
-
147
- async function retryWithFallback() {
148
- const browserWaitUntil = NETWORK_STATES[(NETWORK_STATES.indexOf(options.browserWaitUntil) + 1)];
149
- logData(["Retrying with waitUntil", browserWaitUntil], pageContext);
150
- options.browserWaitUntil = browserWaitUntil;
151
- await closeTarget();
152
- return await getPageData(options);
153
- }
154
-
155
137
  async function shouldRelaunchBrowser() {
156
138
  return Boolean(browserOptions && browserOptions.singleProcess) && await browserExited(BROWSER_EXITED_MAX_DELAY);
157
139
  }
@@ -570,16 +552,28 @@ async function loadPage({ Page, Runtime }, { options, debugMessages }) {
570
552
  // the ID of the top frame is stable, and it is read before the navigation is
571
553
  // triggered so that no event is missed while the browser answers
572
554
  const { frameTree } = await Page.getFrameTree();
555
+ const state = { topFrameId: frameTree.frame.id, reachedStateIndex: -1 };
573
556
  const loadTimeoutAbortController = new AbortController();
574
557
  const loadTimeoutAbortSignal = loadTimeoutAbortController.signal;
575
558
  try {
576
559
  logData(["Loading page", options.url], { options, debugMessages });
560
+ const contextIdPromise = getTopFrameContextId({ Page, Runtime }, state, { options, debugMessages });
577
561
  const [contextId] = await Promise.race([
578
562
  Promise.all([
579
- getTopFrameContextId({ Page, Runtime }, frameTree.frame.id, { options, debugMessages }),
563
+ contextIdPromise,
580
564
  Page.navigate({ url: options.url })
581
565
  ]),
582
- waitForTimeout(loadTimeoutAbortSignal, options.browserLoadMaxTime, LOAD_TIMEOUT_ERROR_MESSAGE, LOAD_TIMEOUT_ERROR)
566
+ waitForTimeout(loadTimeoutAbortSignal, options.browserLoadMaxTime, LOAD_TIMEOUT_ERROR_MESSAGE, LOAD_TIMEOUT_ERROR).catch(async error => {
567
+ if (options.browserWaitUntilFallback && state.reachedStateIndex >= 0) {
568
+ const reachedState = NETWORK_STATES[state.reachedStateIndex];
569
+ logData(["Stopping the page loading, reached state", reachedState], { options, debugMessages });
570
+ console.warn(`Warning: ${options.url} did not reach ${options.browserWaitUntil} within ${options.browserLoadMaxTime} ms, captured as it was at ${reachedState}`); // eslint-disable-line no-console
571
+ await Page.stopLoading();
572
+ state.settle();
573
+ return [await contextIdPromise];
574
+ }
575
+ throw error;
576
+ })
583
577
  ]);
584
578
  // only called when the page loaded, these calls can hang forever on an
585
579
  // unresponsive page and would mask the load timeout error
@@ -596,9 +590,9 @@ async function loadPage({ Page, Runtime }, { options, debugMessages }) {
596
590
 
597
591
  // the listeners are registered synchronously, before the navigation triggered
598
592
  // in parallel by the caller can produce any event
599
- async function getTopFrameContextId({ Page, Runtime }, topFrameId, { options, debugMessages }) {
600
- await waitForPageReadyState({ Page }, { topFrameId }, { options, debugMessages });
601
- return await getSingleFileContext({ Page, Runtime }, topFrameId, { options, debugMessages });
593
+ async function getTopFrameContextId({ Page, Runtime }, state, { options, debugMessages }) {
594
+ await waitForPageReadyState({ Page }, state, { options, debugMessages });
595
+ return await getSingleFileContext({ Page, Runtime }, state.topFrameId, { options, debugMessages });
602
596
  }
603
597
 
604
598
  async function waitForPageReadyState({ Page }, state, { options, debugMessages }) {
@@ -612,6 +606,11 @@ async function waitForPageReadyState({ Page }, state, { options, debugMessages }
612
606
  };
613
607
  const onLifecycleEvent = createLifecycleEventHandler(state, timeoutState, resolve, cleanup, { options, debugMessages });
614
608
  const onFrameNavigated = createFrameNavigatedHandler(timeoutState, reject, cleanup, { options, debugMessages });
609
+ state.settle = () => {
610
+ clearTimeout(timeoutState.timeoutId);
611
+ cleanup();
612
+ resolve();
613
+ };
615
614
  Page.addEventListener(LIFE_CYCLE_EVENT_TYPE, onLifecycleEvent);
616
615
  Page.addEventListener(FRAME_NAVIGATED_EVENT_TYPE, onFrameNavigated);
617
616
  });
@@ -622,6 +621,10 @@ function createLifecycleEventHandler(state, timeoutState, resolve, cleanup, { op
622
621
  const { frameId, name } = params;
623
622
  if (frameId === state.topFrameId) {
624
623
  logData(["Detecting lifecycle event", name], { options, debugMessages });
624
+ const stateIndex = NETWORK_STATES.indexOf(name);
625
+ if (stateIndex != -1 && (state.reachedStateIndex == -1 || stateIndex < state.reachedStateIndex)) {
626
+ state.reachedStateIndex = stateIndex;
627
+ }
625
628
  }
626
629
  const shouldResolve = frameId === state.topFrameId &&
627
630
  (name === options.browserWaitUntil ||