@browserless/cli 13.16.4 → 14.0.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/README.md CHANGED
@@ -51,6 +51,36 @@ The `@browserless/cli` package allows you to:
51
51
  | `ping <url>` | Get response info: status code, redirects, headers |
52
52
  | `status <url>` | Get the HTTP status code |
53
53
  | `goto <url>` | Navigate to a URL and return page/response info |
54
+ | `exec <file>` | Run a script file against a new page and print what it returns |
55
+
56
+ ### Exec examples
57
+
58
+ `exec` takes a file instead of a URL. The file exports a function (CommonJS
59
+ `module.exports` or ES module `export default`). The CLI opens a page and calls
60
+ the function with `{ page, browserless, opts }`:
61
+
62
+ - `page`: a new Puppeteer page in a fresh browser context.
63
+ - `browserless`: the browserless context, for `browserless.goto(page, { url })` and the rest of the API.
64
+ - `opts`: the command-line flags, plus the CLI default `codeScheme`. `--headless` and `--verbose` are consumed by the CLI and not passed. `--timeout` is passed and also sets the browser timeout.
65
+
66
+ ```js
67
+ // title.js
68
+ module.exports = async ({ page, browserless, opts }) => {
69
+ await browserless.goto(page, { url: opts.url })
70
+ return { title: await page.title() }
71
+ }
72
+ ```
73
+
74
+ ```sh
75
+ browserless exec title.js --url=https://example.com
76
+ ```
77
+
78
+ The file path resolves from the current directory. A returned string is printed
79
+ as is and any other value as indented JSON. `undefined` and binary values
80
+ (buffers and typed arrays, such as the result of `page.screenshot()`) print
81
+ nothing, so write binary output to a file from the script. The file is checked
82
+ before the browser starts. A thrown error is
83
+ printed and the process exits with code 1.
54
84
 
55
85
  ### Capture examples
56
86
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@browserless/cli",
3
3
  "description": "Command-line interface for headless browser automation. Take screenshots, generate PDFs, and extract content from websites.",
4
4
  "homepage": "https://browserless.js.org",
5
- "version": "13.16.4",
5
+ "version": "14.0.0",
6
6
  "bin": {
7
7
  "browserless": "src/index.js"
8
8
  },
@@ -34,9 +34,9 @@
34
34
  "web-scraping"
35
35
  ],
36
36
  "dependencies": {
37
- "@browserless/capture": "13.16.4",
37
+ "@browserless/capture": "14.0.0",
38
38
  "beauty-error": "~1.2.23",
39
- "browserless": "13.16.4",
39
+ "browserless": "14.0.0",
40
40
  "dark-mode": "~3.0.0",
41
41
  "dset": "~3.1.4",
42
42
  "mri": "~1.2.0",
@@ -49,6 +49,9 @@
49
49
  "term-img": "~5.0.0",
50
50
  "unique-random-array": "~3.0.0"
51
51
  },
52
+ "devDependencies": {
53
+ "ava": "5"
54
+ },
52
55
  "engines": {
53
56
  "node": ">= 24"
54
57
  },
@@ -56,8 +59,12 @@
56
59
  "src"
57
60
  ],
58
61
  "scripts": {
59
- "test": "exit 0"
62
+ "test": "ava"
60
63
  },
61
64
  "license": "MIT",
62
- "gitHead": "a47151aed56be56d367cb6e5652434c425ef4f85"
65
+ "ava": {
66
+ "timeout": "2m",
67
+ "workerThreads": false
68
+ },
69
+ "gitHead": "bf10b39022a6cc44cccd545756f162bed90dd589"
63
70
  }
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ const { pathToFileURL } = require('url')
4
+ const path = require('path')
5
+ const fs = require('fs')
6
+
7
+ const resolveFile = file => {
8
+ if (!file) throw new TypeError('exec requires a script file: browserless exec <file>')
9
+ const filepath = path.resolve(file)
10
+ if (!fs.existsSync(filepath)) throw new TypeError(`Script file not found: ${filepath}`)
11
+ return filepath
12
+ }
13
+
14
+ const loadScript = async filepath => {
15
+ const { default: script } = await import(pathToFileURL(filepath))
16
+ const run = typeof script === 'function' ? script : script?.default
17
+ if (typeof run !== 'function') throw new TypeError(`${filepath} must export a function.`)
18
+ return run
19
+ }
20
+
21
+ const toBuffer = view => Buffer.from(view.buffer, view.byteOffset, view.byteLength)
22
+
23
+ const serialize = result => {
24
+ if (result === undefined) return ''
25
+ if (typeof result === 'string') return result
26
+ if (ArrayBuffer.isView(result)) return toBuffer(result)
27
+ return JSON.stringify(result, null, 2)
28
+ }
29
+
30
+ module.exports = async ({ file, browserless, opts }) => {
31
+ const run = await loadScript(file)
32
+ const page = await browserless.page()
33
+ return [serialize(await run({ page, browserless, opts }))]
34
+ }
35
+
36
+ module.exports.resolveFile = resolveFile
package/src/help.js CHANGED
@@ -9,6 +9,7 @@ const printCommand = command => `⬩ ${command.replace(path.extname(command), ''
9
9
  module.exports = commands => `
10
10
  Usage
11
11
  $ browserless <command> <url> [flags]
12
+ $ browserless exec <file> [flags]
12
13
 
13
14
  Commands
14
15
  ${gray(commands.map(printCommand).join(EOL + ' '))}`
package/src/index.js CHANGED
@@ -62,9 +62,11 @@ const isPageReady = ({ title = '', bodyText = '', url = '', isWhite = false } =
62
62
  const run = async () => {
63
63
  if (cli.input.length === 0) return cli.showHelp()
64
64
  spinner.start()
65
- const [command, rawUrl] = cli.input
66
- const url = new URL(rawUrl).toString()
65
+ const [command, input] = cli.input
67
66
  const fn = require(`./commands/${command}`)
67
+ const target = fn.resolveFile
68
+ ? { file: fn.resolveFile(input) }
69
+ : { url: new URL(input).toString() }
68
70
  const launchOpts = { headless }
69
71
 
70
72
  if (command === 'capture') {
@@ -87,7 +89,7 @@ const run = async () => {
87
89
  const browserless = await browser.createContext(
88
90
  launchOpts.timeout != null ? { timeout: launchOpts.timeout } : undefined
89
91
  )
90
- return fn({ url, browserless, opts: parsedOpts, isPageReady })
92
+ return fn({ ...target, browserless, opts: parsedOpts, isPageReady })
91
93
  }
92
94
 
93
95
  run()