single-file-cli 2.3.1 → 2.4.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/README.MD CHANGED
@@ -124,6 +124,15 @@ Make sure Chrome or a Chromium-based browser is installed in the default folder.
124
124
  single-file https://www.wikipedia.org wikipedia.html
125
125
  ```
126
126
 
127
+ - Save a page needing a logged-in session. Create the profile first, log in
128
+ to the website in the browser window, and quit the browser to save it. Then
129
+ reuse the profile as many times as needed, it is copied and left unmodified.
130
+
131
+ ```sh
132
+ single-file --create-browser-profile ./profiles/example https://www.example.com/login
133
+ single-file https://www.example.com/account account.html --browser-profile=./profiles/example
134
+ ```
135
+
127
136
  - Save a list of URLs stored into `list-urls.txt` in the current folder
128
137
 
129
138
  ```sh
package/build.sh CHANGED
@@ -1,23 +1,30 @@
1
1
  #!/usr/bin/env bash
2
2
 
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 --minimum-dependency-age=0 "npm:single-file-core@1.5.98"
7
- mv package.json.tmp package.json
8
- mv deno.json.tmp deno.json
9
- mv deno.lock.tmp deno.lock
3
+ set -e
4
+
5
+ CORE_PACKAGE="npm:single-file-core@1.5.107"
6
+ ESBUILD_PACKAGE="npm:esbuild@0.27.7"
7
+
8
+ project_dir=$(pwd)
9
+ build_dir=$(mktemp -d)
10
+ trap 'rm -rf "$build_dir"' EXIT
11
+
12
+ cd "$build_dir"
13
+ deno install --vendor --quiet --minimum-dependency-age=0 "$CORE_PACKAGE"
10
14
 
11
15
  echo "
12
- import { build } from 'npm:esbuild';
16
+ import { build } from '$ESBUILD_PACKAGE';
17
+
18
+ const core = '$build_dir/node_modules/single-file-core';
19
+ const lib = '$project_dir/lib';
13
20
 
14
21
  await build({
15
22
  entryPoints: [
16
- 'node_modules/single-file-core/single-file.js'
23
+ core + '/single-file.js'
17
24
  ],
18
25
  bundle: true,
19
26
  globalName: 'singlefile',
20
- outdir: 'lib/',
27
+ outdir: lib,
21
28
  platform: 'browser',
22
29
  sourcemap: false,
23
30
  minify: true,
@@ -27,11 +34,11 @@ await build({
27
34
 
28
35
  await build({
29
36
  entryPoints: [
30
- 'node_modules/single-file-core/single-file-bootstrap.js'
37
+ core + '/single-file-bootstrap.js'
31
38
  ],
32
39
  bundle: true,
33
40
  globalName: 'singlefileBootstrap',
34
- outdir: 'lib/',
41
+ outdir: lib,
35
42
  platform: 'browser',
36
43
  sourcemap: false,
37
44
  minify: true,
@@ -41,10 +48,10 @@ await build({
41
48
 
42
49
  await build({
43
50
  entryPoints: [
44
- 'node_modules/single-file-core/single-file-hooks-frames.js'
51
+ core + '/single-file-hooks-frames.js'
45
52
  ],
46
53
  bundle: true,
47
- outdir: 'lib/',
54
+ outdir: lib,
48
55
  platform: 'browser',
49
56
  sourcemap: false,
50
57
  minify: true,
@@ -54,11 +61,11 @@ await build({
54
61
 
55
62
  await build({
56
63
  entryPoints: [
57
- 'node_modules/single-file-core/vendor/zip/zip.min.js'
64
+ core + '/vendor/zip/zip.min.js'
58
65
  ],
59
66
  bundle: true,
60
67
  globalName: 'zip',
61
- outdir: 'lib/',
68
+ outdir: lib,
62
69
  platform: 'browser',
63
70
  sourcemap: false,
64
71
  minify: true,
@@ -67,12 +74,11 @@ await build({
67
74
  });
68
75
 
69
76
  await build({
70
- stdin: {
71
- contents: \"export * from './processors/compression/compression.js'; export * from './vendor/zip/zip.js';\",
72
- resolveDir: 'node_modules/single-file-core',
73
- },
77
+ entryPoints: [
78
+ core + '/single-file-archive.js'
79
+ ],
74
80
  bundle: true,
75
- outfile: 'lib/single-file-archive.js',
81
+ outfile: lib + '/single-file-archive.js',
76
82
  platform: 'neutral',
77
83
  sourcemap: false,
78
84
  minify: true,
@@ -81,25 +87,23 @@ await build({
81
87
  });
82
88
 
83
89
  const SCRIPTS = [
84
- 'lib/single-file.js',
85
- 'lib/single-file-bootstrap.js',
86
- 'lib/zip.min.js'
90
+ lib + '/single-file.js',
91
+ lib + '/single-file-bootstrap.js',
92
+ lib + '/zip.min.js'
87
93
  ];
88
94
 
89
95
  let script = '';
90
96
  const scripts = SCRIPTS.map(script => Deno.readTextFile(script));
91
97
  const sources = await Promise.all(scripts);
92
98
  script += 'const script = ' + JSON.stringify(sources.join(';')) + ';';
93
- const hookScript = await Deno.readTextFile('lib/single-file-hooks-frames.js');
99
+ const hookScript = await Deno.readTextFile(lib + '/single-file-hooks-frames.js');
94
100
  script += 'const hookScript = ' + JSON.stringify(hookScript) + ';';
95
- const zipScript = await Deno.readTextFile('lib/zip.min.js');
101
+ const zipScript = await Deno.readTextFile(lib + '/zip.min.js');
96
102
  script += 'const zipScript = ' + JSON.stringify(zipScript) + ';';
97
103
  script += 'export { script, zipScript, hookScript };';
98
- await Deno.writeTextFile('lib/single-file-bundle.js', script)
104
+ await Deno.writeTextFile(lib + '/single-file-bundle.js', script)
99
105
  await Promise.all(SCRIPTS.map(script => Deno.remove(script)));
100
- await Deno.remove('lib/single-file-hooks-frames.js');
101
- const version = JSON.parse(await Deno.readTextFile('./deno.json')).version;
102
- await Deno.writeTextFile('lib/version.js', 'export const version = ' + JSON.stringify(version) + ';');
103
- " | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock=node_modules/deno.lock.tmp -
104
-
105
- rm -rf node_modules
106
+ await Deno.remove(lib + '/single-file-hooks-frames.js');
107
+ const version = JSON.parse(await Deno.readTextFile('$project_dir/deno.json')).version;
108
+ await Deno.writeTextFile(lib + '/version.js', 'export const version = ' + JSON.stringify(version) + ';');
109
+ " | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock="$build_dir/build.lock" -
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@single-file/single-file-cli",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
4
4
  "description": "SingleFile CLI",
5
5
  "exports": {
6
6
  ".": "./single-file-cli-api.js"
package/dev-build.sh CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- mkdir -p node_modules/single-file-core
4
- cp -R ../single-file-core/* node_modules/single-file-core
3
+ set -e
4
+
5
+ ESBUILD_PACKAGE="npm:esbuild@0.27.7"
6
+
7
+ build_dir=$(mktemp -d)
8
+ trap 'rm -rf "$build_dir"' EXIT
5
9
 
6
10
  echo "
7
- import { build } from 'npm:esbuild';
11
+ import { build } from '$ESBUILD_PACKAGE';
8
12
 
9
13
  await build({
10
14
  entryPoints: [
11
- 'node_modules/single-file-core/single-file.js'
15
+ '../single-file-core/single-file.js'
12
16
  ],
13
17
  bundle: true,
14
18
  globalName: 'singlefile',
@@ -22,7 +26,7 @@ await build({
22
26
 
23
27
  await build({
24
28
  entryPoints: [
25
- 'node_modules/single-file-core/single-file-bootstrap.js'
29
+ '../single-file-core/single-file-bootstrap.js'
26
30
  ],
27
31
  bundle: true,
28
32
  globalName: 'singlefileBootstrap',
@@ -36,7 +40,7 @@ await build({
36
40
 
37
41
  await build({
38
42
  entryPoints: [
39
- 'node_modules/single-file-core/single-file-hooks-frames.js'
43
+ '../single-file-core/single-file-hooks-frames.js'
40
44
  ],
41
45
  bundle: true,
42
46
  outdir: 'lib/',
@@ -49,7 +53,7 @@ await build({
49
53
 
50
54
  await build({
51
55
  entryPoints: [
52
- 'node_modules/single-file-core/vendor/zip/zip.min.js'
56
+ '../single-file-core/vendor/zip/zip.min.js'
53
57
  ],
54
58
  bundle: true,
55
59
  globalName: 'zip',
@@ -62,10 +66,9 @@ await build({
62
66
  });
63
67
 
64
68
  await build({
65
- stdin: {
66
- contents: \"export * from './processors/compression/compression.js'; export * from './vendor/zip/zip.js';\",
67
- resolveDir: 'node_modules/single-file-core',
68
- },
69
+ entryPoints: [
70
+ '../single-file-core/single-file-archive.js'
71
+ ],
69
72
  bundle: true,
70
73
  outfile: 'lib/single-file-archive.js',
71
74
  platform: 'neutral',
@@ -95,6 +98,4 @@ await Promise.all(SCRIPTS.map(script => Deno.remove(script)));
95
98
  await Deno.remove('lib/single-file-hooks-frames.js');
96
99
  const version = JSON.parse(await Deno.readTextFile('./deno.json')).version;
97
100
  await Deno.writeTextFile('lib/version.js', 'export const version = ' + JSON.stringify(version) + ';');
98
- " | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock=node_modules/deno.lock.tmp -
99
-
100
- rm -rf node_modules
101
+ " | deno run --allow-read --allow-write --allow-net --allow-run --allow-env --lock="$build_dir/build.lock" -
package/lib/browser.js CHANGED
@@ -21,27 +21,90 @@
21
21
  * Source.
22
22
  */
23
23
 
24
- /* global fetch, setTimeout */
24
+ /* global fetch, setTimeout, WebSocket */
25
25
 
26
26
  import { BROWSER_PATHS, BROWSER_ARGS } from "./constants.js";
27
- import { Deno } from "./deno-polyfill.js";
27
+ import { Deno, path } from "./deno-polyfill.js";
28
28
 
29
29
  const NULL_STD_CONFIG = "null";
30
30
  const DEBUG_PORT_MIN = 9222;
31
31
  const DEBUG_PORT_RANGE = 256;
32
32
  const READY_TIMEOUT = 30000;
33
33
  const READY_RETRY_DELAY = 250;
34
+ const CLOSE_TIMEOUT = 15000;
35
+ const LOCALHOST = "http://localhost:";
36
+ const PROFILE_LOCK_ENTRY_PREFIX = "Singleton";
37
+ const PROFILE_IGNORED_ENTRY_NAMES = [
38
+ "AutofillAiModelCache",
39
+ "Cache",
40
+ "Code Cache",
41
+ "CacheStorage",
42
+ "Crashpad",
43
+ "DawnCache",
44
+ "DawnGraphiteCache",
45
+ "DawnWebGPUCache",
46
+ "GPUCache",
47
+ "GPUPersistentCache",
48
+ "GraphiteDawnCache",
49
+ "GrShaderCache",
50
+ "ShaderCache",
51
+ "component_crx_cache",
52
+ "extensions_crx_cache",
53
+ "optimization_guide_hint_cache_store",
54
+ "optimization_guide_model_store"
55
+ ];
56
+ const PROFILE_INCOMPATIBLE_ARGS = [
57
+ "--no-startup-window",
58
+ "--bwsi",
59
+ "--deny-permission-prompts"
60
+ ];
34
61
 
35
- const { build, makeTempDir, Command, errors, remove } = Deno;
36
- let child, profilePath, childExited;
37
- export { launchBrowser, closeBrowser, browserExited };
62
+ const { build, makeTempDir, readDir, copyFile, mkdir, Command, errors, remove } = Deno;
63
+ const { join } = path;
64
+ let child, profilePath, childExited, keepProfilePath, browserDebugPort;
65
+ export { launchBrowser, createBrowserProfile, getBrowserOptions, copyProfile, closeBrowser, browserExited };
66
+
67
+ function getBrowserOptions(options) {
68
+ return {
69
+ args: options.browserArgs,
70
+ headless: options.browserHeadless,
71
+ executablePath: options.browserExecutablePath,
72
+ debug: options.browserDebug,
73
+ singleProcess: options.browserSingleProcess,
74
+ disableWebSecurity: options.browserDisableWebSecurity,
75
+ width: options.browserWidth,
76
+ height: options.browserHeight,
77
+ userAgent: options.userAgent,
78
+ httpProxyServer: options.httpProxyServer,
79
+ profile: options.browserProfile
80
+ };
81
+ }
82
+
83
+ async function createBrowserProfile(options = {}) {
84
+ await mkdir(options.profile, { recursive: true });
85
+ await launchBrowser(Object.assign({}, options, { headless: false, persistProfile: true }));
86
+ if (child !== undefined) {
87
+ await child.status;
88
+ }
89
+ await closeBrowser();
90
+ }
38
91
 
39
92
  async function launchBrowser(options = {}, indexPath = 0) {
40
93
  const executablePath = options.executablePath || BROWSER_PATHS[build.os][indexPath];
41
94
  let args = Array.from(BROWSER_ARGS);
42
95
  const debugPort = await getDebugPort();
96
+ browserDebugPort = debugPort;
43
97
  args.push("--remote-debugging-port=" + debugPort);
44
- profilePath = await makeTempDir();
98
+ keepProfilePath = Boolean(options.persistProfile);
99
+ if (keepProfilePath) {
100
+ profilePath = options.profile;
101
+ args = args.filter(arg => !PROFILE_INCOMPATIBLE_ARGS.includes(arg));
102
+ } else {
103
+ profilePath = await makeTempDir();
104
+ if (options.profile) {
105
+ await copyProfile(options.profile, profilePath);
106
+ }
107
+ }
45
108
  if (options.headless && !options.debug) {
46
109
  args.push("--headless");
47
110
  } else {
@@ -74,11 +137,16 @@ async function launchBrowser(options = {}, indexPath = 0) {
74
137
  !args.includes("--headless")) {
75
138
  args.push("--disable-site-isolation-trials");
76
139
  }
140
+ if (options.startUrl) {
141
+ args.push(options.startUrl);
142
+ }
77
143
  const command = new Command(executablePath, { args, stdout: NULL_STD_CONFIG, stderr: NULL_STD_CONFIG });
78
144
  try {
79
145
  child = await command.spawn();
80
146
  } catch (error) {
81
- await remove(profilePath, { recursive: true }).catch(() => { });
147
+ if (!keepProfilePath) {
148
+ await remove(profilePath, { recursive: true }).catch(() => { });
149
+ }
82
150
  profilePath = undefined;
83
151
  if (error instanceof errors.NotFound) {
84
152
  if (!options.executablePath && indexPath + 1 < BROWSER_PATHS[build.os].length) {
@@ -155,21 +223,86 @@ function getRandomDebugPort() {
155
223
  }
156
224
 
157
225
  async function closeBrowser() {
158
- if (child !== undefined) {
226
+ const closedChild = child;
227
+ const closedProfilePath = profilePath;
228
+ child = undefined;
229
+ profilePath = undefined;
230
+ if (closedChild !== undefined) {
231
+ if (keepProfilePath && !childExited) {
232
+ await closeBrowserGracefully(closedChild);
233
+ }
159
234
  try {
160
- child.kill();
235
+ closedChild.kill();
161
236
  } catch {
162
237
  // ignored
163
238
  }
164
- await child.status;
165
- child = undefined;
239
+ await closedChild.status;
166
240
  }
167
- if (profilePath !== undefined) {
168
- try {
169
- await remove(profilePath, { recursive: true });
170
- profilePath = undefined;
171
- } catch {
172
- console.log("Warning: failed to remove profile directory: " + profilePath); // eslint-disable-line no-console
241
+ if (closedProfilePath !== undefined) {
242
+ if (keepProfilePath) {
243
+ await removeProfileLockEntries(closedProfilePath).catch(() => { });
244
+ } else {
245
+ try {
246
+ await remove(closedProfilePath, { recursive: true });
247
+ } catch {
248
+ console.log("Warning: failed to remove profile directory: " + closedProfilePath); // eslint-disable-line no-console
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ async function closeBrowserGracefully(closedChild) {
255
+ try {
256
+ const response = await fetch(LOCALHOST + browserDebugPort + "/json/version");
257
+ const { webSocketDebuggerUrl } = await response.json();
258
+ const socket = new WebSocket(webSocketDebuggerUrl);
259
+ await new Promise((resolve, reject) => {
260
+ socket.addEventListener("open", resolve);
261
+ socket.addEventListener("error", reject);
262
+ });
263
+ socket.send(JSON.stringify({ id: 0, method: "Browser.close" }));
264
+ await Promise.race([
265
+ closedChild.status,
266
+ new Promise(resolve => setTimeout(resolve, CLOSE_TIMEOUT))
267
+ ]);
268
+ } catch {
269
+ // ignored
270
+ }
271
+ }
272
+
273
+ async function copyProfile(sourcePath, destinationPath) {
274
+ let entries;
275
+ try {
276
+ entries = await readDir(sourcePath);
277
+ } catch (error) {
278
+ throw error instanceof errors.NotFound ?
279
+ new Error(`The browser profile directory was not found at ${JSON.stringify(sourcePath)}`) :
280
+ error;
281
+ }
282
+ await copyProfileEntries(entries, sourcePath, destinationPath);
283
+ }
284
+
285
+ async function copyProfileEntries(entries, sourcePath, destinationPath) {
286
+ for (const entry of entries) {
287
+ if (entry.isSymlink || entry.name.startsWith(PROFILE_LOCK_ENTRY_PREFIX) || PROFILE_IGNORED_ENTRY_NAMES.includes(entry.name)) {
288
+ continue;
289
+ }
290
+ const entrySourcePath = join(sourcePath, entry.name);
291
+ const entryDestinationPath = join(destinationPath, entry.name);
292
+ if (entry.isDirectory) {
293
+ await mkdir(entryDestinationPath, { recursive: true });
294
+ await copyProfileEntries(await readDir(entrySourcePath), entrySourcePath, entryDestinationPath);
295
+ } else {
296
+ await copyFile(entrySourcePath, entryDestinationPath);
297
+ }
298
+ }
299
+ }
300
+
301
+ async function removeProfileLockEntries(profileDirectory) {
302
+ const entries = await readDir(profileDirectory);
303
+ for (const entry of entries) {
304
+ if (entry.name.startsWith(PROFILE_LOCK_ENTRY_PREFIX)) {
305
+ await remove(join(profileDirectory, entry.name)).catch(() => { });
173
306
  }
174
307
  }
175
308
  }
package/lib/cdp-client.js CHANGED
@@ -25,6 +25,7 @@
25
25
 
26
26
  import {
27
27
  launchBrowser,
28
+ getBrowserOptions,
28
29
  closeBrowser,
29
30
  browserExited
30
31
  } from "./browser.js";
@@ -59,6 +60,7 @@ const SET_PDF_FUNCTION_NAME = "setPDF";
59
60
  const SET_PAGE_DATA_FUNCTION_NAME = "setPageData";
60
61
  const BINDING_CALLED_EVENT_TYPE = "bindingCalled";
61
62
  const LOCALHOST = "http://localhost:";
63
+ const HEADLESS_USER_AGENT_TOKEN = "Headless";
62
64
  const BROWSER_EXITED_MAX_DELAY = 2000;
63
65
 
64
66
  let browserOptions, relaunchBrowserPromise;
@@ -73,17 +75,7 @@ async function initialize(singleFileOptions) {
73
75
  if (singleFileOptions.browserServer) {
74
76
  options.apiUrl = singleFileOptions.browserServer;
75
77
  } else {
76
- browserOptions = {};
77
- browserOptions.args = singleFileOptions.browserArgs;
78
- browserOptions.headless = singleFileOptions.browserHeadless;
79
- browserOptions.executablePath = singleFileOptions.browserExecutablePath;
80
- browserOptions.debug = singleFileOptions.browserDebug;
81
- browserOptions.singleProcess = singleFileOptions.browserSingleProcess;
82
- browserOptions.disableWebSecurity = singleFileOptions.browserDisableWebSecurity;
83
- browserOptions.width = singleFileOptions.browserWidth;
84
- browserOptions.height = singleFileOptions.browserHeight;
85
- browserOptions.userAgent = singleFileOptions.userAgent;
86
- browserOptions.httpProxyServer = singleFileOptions.httpProxyServer;
78
+ browserOptions = getBrowserOptions(singleFileOptions);
87
79
  options.apiUrl = LOCALHOST + (await launchBrowser(browserOptions));
88
80
  }
89
81
  }
@@ -221,9 +213,7 @@ async function setupDeviceEmulation({ Browser, Emulation, Runtime }, { options,
221
213
  if (needsDeviceMetrics) {
222
214
  await setupDeviceMetrics({ Emulation, Runtime }, { options, debugMessages });
223
215
  }
224
- if (needsUserAgentOverride(options)) {
225
- await setupUserAgent({ Browser, Emulation }, { options, debugMessages });
226
- }
216
+ await setupUserAgent({ Browser, Emulation }, { options, debugMessages });
227
217
  }
228
218
 
229
219
  function needsUserAgentOverride(options) {
@@ -253,9 +243,9 @@ async function setupDeviceMetrics({ Emulation, Runtime }, { options, debugMessag
253
243
  async function setupUserAgent({ Browser, Emulation }, { options, debugMessages }, sessionId) {
254
244
  const ANDROID_PLATFORM = "Android";
255
245
  const { userAgent, product } = await Browser.getVersion();
256
- const defaultMobileUA = `Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) ${product} Mobile Safari/537.36`;
246
+ const defaultMobileUA = `Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) ${removeHeadlessToken(product)} Mobile Safari/537.36`;
257
247
  const agentOptions = {
258
- userAgent: options.userAgent || (options.browserMobileEmulation ? defaultMobileUA : userAgent)
248
+ userAgent: options.userAgent || (options.browserMobileEmulation ? defaultMobileUA : removeHeadlessToken(userAgent))
259
249
  };
260
250
  if (options.acceptLanguage) {
261
251
  agentOptions.acceptLanguage = options.acceptLanguage;
@@ -263,8 +253,14 @@ async function setupUserAgent({ Browser, Emulation }, { options, debugMessages }
263
253
  if (options.platform || options.browserMobileEmulation) {
264
254
  agentOptions.platform = options.platform || ANDROID_PLATFORM;
265
255
  }
266
- logData(["Emulating user agent", JSON.stringify(agentOptions)], { options, debugMessages });
267
- await Emulation.setUserAgentOverride(agentOptions, sessionId);
256
+ if (needsUserAgentOverride(options) || agentOptions.userAgent !== userAgent) {
257
+ logData(["Emulating user agent", JSON.stringify(agentOptions)], { options, debugMessages });
258
+ await Emulation.setUserAgentOverride(agentOptions, sessionId);
259
+ }
260
+ }
261
+
262
+ function removeHeadlessToken(userAgent) {
263
+ return userAgent.replace(HEADLESS_USER_AGENT_TOKEN, "");
268
264
  }
269
265
 
270
266
  async function setupNetworkInterception({ Browser, Emulation, Fetch, Network }, { options, debugMessages, httpInfo, blockedURLPatterns }) {
@@ -481,9 +477,7 @@ async function setupFrameSession({ Browser, Emulation, Fetch, Network, Security
481
477
  if (options.emulateMediaFeatures) {
482
478
  await setupMediaFeatures({ Emulation }, { options, debugMessages }, sessionId);
483
479
  }
484
- if (needsUserAgentOverride(options)) {
485
- await setupUserAgent({ Browser, Emulation }, { options, debugMessages }, sessionId);
486
- }
480
+ await setupUserAgent({ Browser, Emulation }, { options, debugMessages }, sessionId);
487
481
  }
488
482
 
489
483
  async function getContextId({ Debugger, Page, Runtime }, { options, debugMessages }) {
package/lib/constants.js CHANGED
@@ -43,7 +43,6 @@ const BROWSER_ARGS = [
43
43
  "--disable-renderer-backgrounding",
44
44
  "--force-color-profile=srgb",
45
45
  "--no-first-run",
46
- "--enable-automation",
47
46
  "--password-store=basic",
48
47
  "--use-mock-keychain",
49
48
  "--no-service-autorun",
@@ -101,8 +101,10 @@ const DenoAPI = {
101
101
  args,
102
102
  readFile,
103
103
  readTextFile,
104
+ readDir,
104
105
  writeTextFile,
105
106
  writeFile,
107
+ copyFile,
106
108
  mkdir,
107
109
  makeTempDir,
108
110
  stat,
@@ -118,6 +120,7 @@ const DenoAPI = {
118
120
 
119
121
  const pathAPI = {
120
122
  dirname,
123
+ join,
121
124
  toFileUrl,
122
125
  fromFileUrl,
123
126
  SEPARATOR: DENO_RUNTIME_DETECTED ? path.SEPARATOR : path.sep
@@ -154,6 +157,41 @@ async function readTextFile(path) {
154
157
  }
155
158
  }
156
159
 
160
+ async function readDir(path) {
161
+ if (DENO_RUNTIME_DETECTED) {
162
+ const entries = [];
163
+ try {
164
+ for await (const entry of Deno.readDir(path)) {
165
+ entries.push({ name: entry.name, isDirectory: entry.isDirectory, isSymlink: entry.isSymlink });
166
+ }
167
+ } catch (error) {
168
+ throw mapNotFoundError(error);
169
+ }
170
+ return entries;
171
+ } else {
172
+ const fsPromise = await import(NODE_MODULES["fs"]);
173
+ try {
174
+ const entries = await fsPromise.readdir(path, { withFileTypes: true });
175
+ return entries.map(entry => ({ name: entry.name, isDirectory: entry.isDirectory(), isSymlink: entry.isSymbolicLink() }));
176
+ } catch (error) {
177
+ throw mapNotFoundError(error);
178
+ }
179
+ }
180
+ }
181
+
182
+ async function copyFile(sourcePath, destinationPath) {
183
+ if (DENO_RUNTIME_DETECTED) {
184
+ return Deno.copyFile(sourcePath, destinationPath);
185
+ } else {
186
+ const fsPromise = await import(NODE_MODULES["fs"]);
187
+ return fsPromise.copyFile(sourcePath, destinationPath);
188
+ }
189
+ }
190
+
191
+ function mapNotFoundError(error) {
192
+ return error.code == "ENOENT" || error.code == "ENOTDIR" ? new errors.NotFound(error.message) : error;
193
+ }
194
+
157
195
  async function writeTextFile(path, data, options = {}) {
158
196
  if (DENO_RUNTIME_DETECTED) {
159
197
  return Deno.writeTextFile(path, data, options);
@@ -259,6 +297,10 @@ function dirname(filePath) {
259
297
  return path.dirname(filePath);
260
298
  }
261
299
 
300
+ function join(...filePaths) {
301
+ return path.join(...filePaths);
302
+ }
303
+
262
304
  async function toFileUrl(filePath) {
263
305
  if (DENO_RUNTIME_DETECTED) {
264
306
  return path.toFileUrl(filePath);