single-file-cli 2.6.4 → 2.7.1
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 +1 -1
- package/deno.json +1 -1
- package/lib/browser.js +30 -3
- package/lib/cdp-client.js +42 -30
- package/lib/single-file-archive.js +7 -7
- package/lib/single-file-bundle.js +1 -1
- package/lib/version.js +1 -1
- package/options.js +2 -2
- package/package.json +3 -2
- package/test/e2e/lazy-loading.test.js +204 -0
- package/test/e2e/navigation.test.js +43 -0
- package/test/e2e/timeouts.test.js +101 -14
- package/update-core.sh +9 -0
package/build.sh
CHANGED
package/deno.json
CHANGED
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
|
|
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
|
-
|
|
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 },
|
|
600
|
-
await waitForPageReadyState({ Page },
|
|
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 }) {
|
|
@@ -611,7 +605,12 @@ async function waitForPageReadyState({ Page }, state, { options, debugMessages }
|
|
|
611
605
|
Page.removeEventListener(FRAME_NAVIGATED_EVENT_TYPE, onFrameNavigated);
|
|
612
606
|
};
|
|
613
607
|
const onLifecycleEvent = createLifecycleEventHandler(state, timeoutState, resolve, cleanup, { options, debugMessages });
|
|
614
|
-
const onFrameNavigated = createFrameNavigatedHandler(timeoutState, reject, cleanup, { options, debugMessages });
|
|
608
|
+
const onFrameNavigated = createFrameNavigatedHandler(state, 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 ||
|
|
@@ -640,15 +643,24 @@ function createLifecycleEventHandler(state, timeoutState, resolve, cleanup, { op
|
|
|
640
643
|
};
|
|
641
644
|
};
|
|
642
645
|
|
|
643
|
-
function createFrameNavigatedHandler(timeoutState, reject, cleanup, { options, debugMessages }) {
|
|
646
|
+
function createFrameNavigatedHandler(state, timeoutState, reject, cleanup, { options, debugMessages }) {
|
|
644
647
|
const UNREACHABLE_URL_ERROR_MESSAGE = "Unreachable URL";
|
|
645
648
|
return ({ params }) => {
|
|
646
649
|
const { frame } = params;
|
|
647
|
-
if (!frame.parentId
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
650
|
+
if (!frame.parentId) {
|
|
651
|
+
if (frame.unreachableUrl) {
|
|
652
|
+
logData(["Detecting unreachable URL", frame.unreachableUrl], { options, debugMessages });
|
|
653
|
+
clearTimeout(timeoutState.timeoutId);
|
|
654
|
+
cleanup();
|
|
655
|
+
reject(new Error(UNREACHABLE_URL_ERROR_MESSAGE + ": " + frame.unreachableUrl));
|
|
656
|
+
} else {
|
|
657
|
+
// a new document starts the wait over: the states the previous one
|
|
658
|
+
// reached and the delay it started say nothing about this one
|
|
659
|
+
logData(["Detecting navigation", frame.url], { options, debugMessages });
|
|
660
|
+
clearTimeout(timeoutState.timeoutId);
|
|
661
|
+
timeoutState.timeoutId = undefined;
|
|
662
|
+
state.reachedStateIndex = -1;
|
|
663
|
+
}
|
|
652
664
|
}
|
|
653
665
|
};
|
|
654
666
|
}
|