single-file-cli 2.0.19 → 2.0.21
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 +0 -6
- package/deno.json +2 -2
- package/deno.lock +8 -8
- package/lib/browser.js +1 -7
- package/lib/cdp-client.js +106 -42
- package/lib/version.js +1 -1
- package/options.js +1 -0
- package/package.json +2 -2
- package/single-file-cli-api.js +16 -2
package/README.MD
CHANGED
|
@@ -80,9 +80,6 @@ Make sure Chrome or a Chromium-based browser is installed in the default folder.
|
|
|
80
80
|
|
|
81
81
|
```sh
|
|
82
82
|
unzip master.zip .
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
```sh
|
|
86
83
|
cd single-file-cli-master
|
|
87
84
|
```
|
|
88
85
|
|
|
@@ -90,9 +87,6 @@ Make sure Chrome or a Chromium-based browser is installed in the default folder.
|
|
|
90
87
|
|
|
91
88
|
```sh
|
|
92
89
|
git clone --depth 1 --recursive https://github.com/gildas-lormeau/single-file-cli.git
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
```sh
|
|
96
90
|
cd single-file-cli
|
|
97
91
|
```
|
|
98
92
|
|
package/deno.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@single-file/single-file-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
4
4
|
"description": "SingleFile CLI",
|
|
5
5
|
"nodeModulesDir": true,
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./single-file-cli-api.js"
|
|
8
8
|
},
|
|
9
9
|
"imports": {
|
|
10
|
-
"simple-cdp": "jsr:@simple-cdp/simple-cdp@^1.8.
|
|
10
|
+
"simple-cdp": "jsr:@simple-cdp/simple-cdp@^1.8.4",
|
|
11
11
|
"path": "jsr:@std/path@^0.220.1"
|
|
12
12
|
}
|
|
13
13
|
}
|
package/deno.lock
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
"version": "3",
|
|
3
3
|
"packages": {
|
|
4
4
|
"specifiers": {
|
|
5
|
-
"jsr:@simple-cdp/simple-cdp@^1.8.
|
|
5
|
+
"jsr:@simple-cdp/simple-cdp@^1.8.4": "jsr:@simple-cdp/simple-cdp@1.8.4",
|
|
6
6
|
"jsr:@std/assert@^0.220.1": "jsr:@std/assert@0.220.1",
|
|
7
7
|
"jsr:@std/path@^0.220.1": "jsr:@std/path@0.220.1",
|
|
8
|
-
"npm:simple-cdp@^1.8.
|
|
8
|
+
"npm:simple-cdp@^1.8.4": "npm:simple-cdp@1.8.4",
|
|
9
9
|
"npm:ws@^8.16.0": "npm:ws@8.16.0"
|
|
10
10
|
},
|
|
11
11
|
"jsr": {
|
|
12
|
-
"@simple-cdp/simple-cdp@1.8.
|
|
13
|
-
"integrity": "
|
|
12
|
+
"@simple-cdp/simple-cdp@1.8.4": {
|
|
13
|
+
"integrity": "5e31ab90a552f9f1cf84e6cf0454d21c5b9dc1d89f9c1f9a750772b47bad9a4e"
|
|
14
14
|
},
|
|
15
15
|
"@std/assert@0.220.1": {
|
|
16
16
|
"integrity": "88710d54f3afdd7a5761e7805abba1f56cd14e4b212feffeb3e73a9f77482425"
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"npm": {
|
|
26
|
-
"simple-cdp@1.8.
|
|
27
|
-
"integrity": "sha512-
|
|
26
|
+
"simple-cdp@1.8.4": {
|
|
27
|
+
"integrity": "sha512-fsvMUyz13vzX8nxSPwJfdxdROGxRDdTAFX7XitVpRsKwClowsyL3NZ6kR4vR5tZfxbz31p3uoM14eFUvhaWisA==",
|
|
28
28
|
"dependencies": {}
|
|
29
29
|
},
|
|
30
30
|
"ws@8.16.0": {
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
"remote": {},
|
|
37
37
|
"workspace": {
|
|
38
38
|
"dependencies": [
|
|
39
|
-
"jsr:@simple-cdp/simple-cdp@^1.8.
|
|
39
|
+
"jsr:@simple-cdp/simple-cdp@^1.8.4",
|
|
40
40
|
"jsr:@std/path@^0.220.1"
|
|
41
41
|
],
|
|
42
42
|
"packageJson": {
|
|
43
43
|
"dependencies": [
|
|
44
|
-
"npm:simple-cdp@^1.8.
|
|
44
|
+
"npm:simple-cdp@^1.8.4",
|
|
45
45
|
"npm:ws@^8.16.0"
|
|
46
46
|
]
|
|
47
47
|
}
|
package/lib/browser.js
CHANGED
|
@@ -66,13 +66,7 @@ async function launchBrowser(options = {}, indexPath = 0) {
|
|
|
66
66
|
if (indexPath + 1 < BROWSER_PATHS[build.os].length) {
|
|
67
67
|
await launchBrowser(options, indexPath + 1);
|
|
68
68
|
} else {
|
|
69
|
-
|
|
70
|
-
if (options.executablePath) {
|
|
71
|
-
message += "Make sure --browser-executable-path is correct.";
|
|
72
|
-
} else {
|
|
73
|
-
message += "Set the path using the --browser-executable-path option.";
|
|
74
|
-
}
|
|
75
|
-
throw new Error(message);
|
|
69
|
+
throw error;
|
|
76
70
|
}
|
|
77
71
|
} else {
|
|
78
72
|
throw error;
|
package/lib/cdp-client.js
CHANGED
|
@@ -37,13 +37,17 @@ const EMPTY_PAGE_URL = "about:blank";
|
|
|
37
37
|
export { initialize, getPageData, closeBrowser };
|
|
38
38
|
|
|
39
39
|
async function initialize(options) {
|
|
40
|
-
|
|
40
|
+
if (!options.browserRemoteDebuggingUrl) {
|
|
41
|
+
await launchBrowser(getBrowserOptions(options));
|
|
42
|
+
} else {
|
|
43
|
+
CDP.remoteDebuggingUrl = options.browserRemoteDebuggingUrl;
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
async function getPageData(options) {
|
|
44
48
|
let targetInfo;
|
|
45
49
|
try {
|
|
46
|
-
|
|
50
|
+
targetInfo = await CDP.createTarget(EMPTY_PAGE_URL);
|
|
47
51
|
const { Browser, Security, Page, Emulation, Fetch, Network, Runtime, Debugger } = new CDP(targetInfo);
|
|
48
52
|
if (options.browserStartMinimized) {
|
|
49
53
|
const { windowId, bounds } = await Browser.getWindowForTarget({ targetId: targetInfo.targetId });
|
|
@@ -96,16 +100,22 @@ async function getPageData(options) {
|
|
|
96
100
|
worldName: SINGLE_FILE_WORLD_NAME
|
|
97
101
|
});
|
|
98
102
|
const [contextId] = await Promise.all([
|
|
99
|
-
|
|
103
|
+
loadPage({ Page, Runtime }, options),
|
|
100
104
|
options.browserDebug ? waitForDebuggerReady({ Debugger }) : Promise.resolve()
|
|
101
105
|
]);
|
|
106
|
+
if (options.browserWaitDelay) {
|
|
107
|
+
setTimeout(() => resolve, options.browserWaitDelay);
|
|
108
|
+
}
|
|
102
109
|
const { result } = await Runtime.evaluate({
|
|
103
110
|
expression: `singlefile.getPageData(${JSON.stringify(options)})`,
|
|
104
111
|
awaitPromise: true,
|
|
105
112
|
returnByValue: true,
|
|
106
113
|
contextId
|
|
107
114
|
});
|
|
108
|
-
const { value } = result;
|
|
115
|
+
const { value, subtype, description } = result;
|
|
116
|
+
if (subtype === "error") {
|
|
117
|
+
throw new Error(description);
|
|
118
|
+
}
|
|
109
119
|
if (options.compressContent) {
|
|
110
120
|
value.content = new Uint8Array(value.content);
|
|
111
121
|
}
|
|
@@ -121,12 +131,12 @@ async function getPageData(options) {
|
|
|
121
131
|
throw error;
|
|
122
132
|
} finally {
|
|
123
133
|
if (targetInfo && !options.browserDebug) {
|
|
124
|
-
await CDP.closeTarget(targetInfo.
|
|
134
|
+
await CDP.closeTarget(targetInfo.id);
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
137
|
}
|
|
128
138
|
|
|
129
|
-
async function
|
|
139
|
+
async function loadPage({ Page, Runtime }, options) {
|
|
130
140
|
await Runtime.enable();
|
|
131
141
|
await Page.enable();
|
|
132
142
|
try {
|
|
@@ -134,7 +144,7 @@ async function navigate({ Page, Runtime }, options) {
|
|
|
134
144
|
const loadTimeoutAbortSignal = loadTimeoutAbortController.signal;
|
|
135
145
|
const [contextId] = await Promise.race([
|
|
136
146
|
Promise.all([getTopFrameContextId({ Page, Runtime }, options), Page.navigate({ url: options.url })]),
|
|
137
|
-
|
|
147
|
+
waitForLoadTimeout(loadTimeoutAbortSignal, options.browserLoadMaxTime)
|
|
138
148
|
]);
|
|
139
149
|
if (!loadTimeoutAbortSignal.aborted) {
|
|
140
150
|
loadTimeoutAbortController.abort();
|
|
@@ -146,34 +156,83 @@ async function navigate({ Page, Runtime }, options) {
|
|
|
146
156
|
}
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
await new Promise(resolve => {
|
|
159
|
+
function getTopFrameContextId({ Page, Runtime }, options) {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
152
161
|
const FRAME_NAVIGATED_EVENT = "frameNavigated";
|
|
153
162
|
const CONTEXT_CREATED_EVENT = "executionContextCreated";
|
|
163
|
+
const CONTEXT_DESTROYED_EVENT = "executionContextDestroyed";
|
|
164
|
+
const contextIds = [];
|
|
165
|
+
let frameId, navigationAbortController;
|
|
154
166
|
Page.addEventListener(FRAME_NAVIGATED_EVENT, onFrameNavigated);
|
|
155
|
-
Runtime.addEventListener(CONTEXT_CREATED_EVENT,
|
|
167
|
+
Runtime.addEventListener(CONTEXT_CREATED_EVENT, onExecutionContextCreated);
|
|
168
|
+
Runtime.addEventListener(CONTEXT_DESTROYED_EVENT, onExecutionContextDestroyed);
|
|
156
169
|
|
|
157
170
|
async function onFrameNavigated({ params }) {
|
|
158
171
|
const { frame } = params;
|
|
159
172
|
if (!frame.parentId) {
|
|
160
173
|
frameId = frame.id;
|
|
161
|
-
|
|
174
|
+
if (navigationAbortController) {
|
|
175
|
+
navigationAbortController.abort();
|
|
176
|
+
contextIds.length = 0;
|
|
177
|
+
}
|
|
178
|
+
navigationAbortController = new AbortController();
|
|
179
|
+
await onTopFrameNavigated(navigationAbortController.signal);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function onTopFrameNavigated(navigationSignal) {
|
|
184
|
+
await waitForPageReady({ Page }, frameId, options, navigationSignal);
|
|
185
|
+
if (!navigationSignal.aborted) {
|
|
162
186
|
Page.removeEventListener(FRAME_NAVIGATED_EVENT, onFrameNavigated);
|
|
163
|
-
Runtime.removeEventListener(CONTEXT_CREATED_EVENT,
|
|
164
|
-
|
|
187
|
+
Runtime.removeEventListener(CONTEXT_CREATED_EVENT, onExecutionContextCreated);
|
|
188
|
+
Runtime.removeEventListener(CONTEXT_DESTROYED_EVENT, onExecutionContextDestroyed);
|
|
189
|
+
let contextId;
|
|
190
|
+
if (contextIds.length) {
|
|
191
|
+
let contextIdIndex = 0;
|
|
192
|
+
do {
|
|
193
|
+
if (await testExecutionContext(contextIds[contextIdIndex])) {
|
|
194
|
+
contextId = contextIds[contextIdIndex];
|
|
195
|
+
}
|
|
196
|
+
contextIdIndex++;
|
|
197
|
+
} while (contextId === undefined && contextIdIndex < contextIds.length);
|
|
198
|
+
}
|
|
199
|
+
if (contextId === undefined) {
|
|
200
|
+
reject(new Error("Execution context not found"));
|
|
201
|
+
} else {
|
|
202
|
+
resolve(contextId);
|
|
203
|
+
}
|
|
165
204
|
}
|
|
166
205
|
}
|
|
167
206
|
|
|
168
|
-
function
|
|
207
|
+
function onExecutionContextCreated({ params }) {
|
|
169
208
|
const { context } = params;
|
|
170
209
|
const { name, origin, auxData } = context;
|
|
171
210
|
if (frameId !== undefined && origin === "://" && name === SINGLE_FILE_WORLD_NAME && auxData && auxData.frameId === frameId) {
|
|
172
|
-
|
|
211
|
+
contextIds.push(context.id);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function onExecutionContextDestroyed({ params }) {
|
|
216
|
+
const { executionContextId } = params;
|
|
217
|
+
const index = contextIds.indexOf(executionContextId);
|
|
218
|
+
if (index > -1) {
|
|
219
|
+
contextIds.splice(index, 1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function testExecutionContext(contextId) {
|
|
224
|
+
try {
|
|
225
|
+
const { result } = await Runtime.evaluate({
|
|
226
|
+
expression: "singlefile !== undefined",
|
|
227
|
+
contextId
|
|
228
|
+
});
|
|
229
|
+
return result.value === true;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
// ignored
|
|
173
232
|
}
|
|
233
|
+
return false;
|
|
174
234
|
}
|
|
175
235
|
});
|
|
176
|
-
return contextId;
|
|
177
236
|
}
|
|
178
237
|
|
|
179
238
|
function waitForLoadTimeout(abortSignal, maxDelay) {
|
|
@@ -194,41 +253,46 @@ function waitForLoadTimeout(abortSignal, maxDelay) {
|
|
|
194
253
|
});
|
|
195
254
|
}
|
|
196
255
|
|
|
197
|
-
async function waitForPageReady({ Page }, topFrameId, options) {
|
|
256
|
+
async function waitForPageReady({ Page }, topFrameId, options, navigationSignal) {
|
|
198
257
|
await Page.setLifecycleEventsEnabled({ enabled: true });
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
258
|
+
try {
|
|
259
|
+
await new Promise(resolve => {
|
|
260
|
+
const LIFE_CYCLE_EVENT = "lifecycleEvent";
|
|
261
|
+
Page.addEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
|
|
262
|
+
|
|
263
|
+
function onLifecycleEvent({ params }) {
|
|
264
|
+
const { name, frameId } = params;
|
|
265
|
+
if (frameId === topFrameId) {
|
|
266
|
+
if (name === options.browserWaitUntil || name === INTERACTIVE_TIME_STATE || navigationSignal.aborted) {
|
|
267
|
+
Page.removeEventListener(LIFE_CYCLE_EVENT, onLifecycleEvent);
|
|
268
|
+
resolve();
|
|
269
|
+
}
|
|
209
270
|
}
|
|
210
271
|
}
|
|
272
|
+
});
|
|
273
|
+
} finally {
|
|
274
|
+
if (!navigationSignal.aborted) {
|
|
275
|
+
await Page.setLifecycleEventsEnabled({ enabled: false });
|
|
211
276
|
}
|
|
212
|
-
});
|
|
213
|
-
await Page.setLifecycleEventsEnabled({ enabled: false });
|
|
214
|
-
if (options.browserWaitDelay) {
|
|
215
|
-
setTimeout(() => resolve, options.browserWaitDelay);
|
|
216
277
|
}
|
|
217
278
|
}
|
|
218
279
|
|
|
219
280
|
async function waitForDebuggerReady({ Debugger }) {
|
|
220
281
|
await Debugger.enable();
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
282
|
+
try {
|
|
283
|
+
await Debugger.pause();
|
|
284
|
+
await new Promise(resolve => {
|
|
285
|
+
const RESUMED_EVENT = "resumed";
|
|
286
|
+
Debugger.addEventListener(RESUMED_EVENT, onResumed);
|
|
225
287
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
288
|
+
function onResumed() {
|
|
289
|
+
Debugger.removeEventListener(RESUMED_EVENT, onResumed);
|
|
290
|
+
resolve();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
} finally {
|
|
294
|
+
await Debugger.disable();
|
|
295
|
+
}
|
|
232
296
|
}
|
|
233
297
|
|
|
234
298
|
function getBrowserOptions(options) {
|
package/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "2.0.
|
|
1
|
+
export const version = "2.0.21";
|
package/options.js
CHANGED
|
@@ -60,6 +60,7 @@ const OPTIONS_INFO = {
|
|
|
60
60
|
"browser-cookie": { description: "Ordered list of cookie parameters separated by a comma (name,value,domain,path,expires,httpOnly,secure,sameSite,url)", type: "string[]" },
|
|
61
61
|
"browser-cookies-file": { description: "Path of the cookies file formatted as a JSON file or a Netscape text file", type: "string" },
|
|
62
62
|
"browser-ignore-insecure-certs": { description: "Ignore HTTPs errors", type: "boolean" },
|
|
63
|
+
"browser-remote-debugging-url": { description: "Remote debugging URL", type: "string" },
|
|
63
64
|
"compress-content": { description: "Compress the output file into a ZIP file", type: "boolean" },
|
|
64
65
|
"compress-CSS": { description: "Compress CSS stylesheets", type: "boolean" },
|
|
65
66
|
"compress-HTML": { description: "Compress HTML content", type: "boolean", defaultValue: true },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "single-file-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
4
4
|
"description": "SingleFile CLI",
|
|
5
5
|
"author": "Gildas Lormeau",
|
|
6
6
|
"engines": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"single-file": "./single-file-node.js"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"simple-cdp": "^1.8.
|
|
16
|
+
"simple-cdp": "^1.8.4",
|
|
17
17
|
"ws": "^8.16.0"
|
|
18
18
|
}
|
|
19
19
|
}
|
package/single-file-cli-api.js
CHANGED
|
@@ -76,7 +76,7 @@ const DEFAULT_OPTIONS = {
|
|
|
76
76
|
const STATE_PROCESSING = "processing";
|
|
77
77
|
const STATE_PROCESSED = "processed";
|
|
78
78
|
|
|
79
|
-
const { readTextFile, writeTextFile, writeFile, stdout, mkdir, stat } = Deno;
|
|
79
|
+
const { readTextFile, writeTextFile, writeFile, stdout, mkdir, stat, errors } = Deno;
|
|
80
80
|
let tasks = [], maxParallelWorkers, sessionFilename;
|
|
81
81
|
|
|
82
82
|
export { DEFAULT_OPTIONS, VALID_URL_TEST, initialize };
|
|
@@ -84,7 +84,21 @@ export { DEFAULT_OPTIONS, VALID_URL_TEST, initialize };
|
|
|
84
84
|
async function initialize(options) {
|
|
85
85
|
options = Object.assign({}, DEFAULT_OPTIONS, options);
|
|
86
86
|
maxParallelWorkers = options.maxParallelWorkers || 8;
|
|
87
|
-
|
|
87
|
+
try {
|
|
88
|
+
await backend.initialize(options);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error instanceof errors.NotFound) {
|
|
91
|
+
let message = "Chromium executable not found. ";
|
|
92
|
+
if (options.browserExecutablePath) {
|
|
93
|
+
message += "Make sure --browser-executable-path is correct.";
|
|
94
|
+
} else {
|
|
95
|
+
message += "Set the path using the --browser-executable-path option.";
|
|
96
|
+
}
|
|
97
|
+
throw new Error(message);
|
|
98
|
+
} else {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
88
102
|
if (options.crawlSyncSession || options.crawlLoadSession) {
|
|
89
103
|
try {
|
|
90
104
|
tasks = JSON.parse(await readTextFile(options.crawlSyncSession || options.crawlLoadSession));
|