microlink.io 0.0.4 → 0.1.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/bin/help.txt +7 -1
- package/bin/index.js +247 -14
- package/package.json +2 -3
- package/src/index.js +27 -12
package/bin/help.txt
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Usage
|
|
2
|
+
$ microlink <url> [options]
|
|
2
3
|
$ microlink <product> <url|query> [options]
|
|
3
4
|
|
|
4
5
|
Products
|
|
5
|
-
metadata Unified metadata (title, description, image, ...)
|
|
6
|
+
metadata Unified metadata (title, description, image, ...); default
|
|
6
7
|
logo Brand logo of the site (--square prefers the square variant)
|
|
7
8
|
markdown Page content as Markdown
|
|
8
9
|
html Page content as HTML
|
|
@@ -31,12 +32,17 @@ Options
|
|
|
31
32
|
--header, -H Extra request header as 'Name: value' (repeatable)
|
|
32
33
|
--data JSON data rules for the extract command
|
|
33
34
|
--file Path to the code file for the function command
|
|
35
|
+
--trace Print request & response payload (API key masked)
|
|
36
|
+
--trace-full Same as --trace, including the full API key
|
|
34
37
|
--help Show this help
|
|
35
38
|
|
|
36
39
|
Any other flag is passed as an option to the product, e.g. --fullPage,
|
|
37
40
|
--device 'iPhone 11', --waitUntil networkidle0, --selector article.
|
|
38
41
|
|
|
39
42
|
Examples
|
|
43
|
+
$ microlink https://example.com
|
|
44
|
+
$ microlink https://example.com --trace
|
|
45
|
+
$ microlink https://example.com --trace-full
|
|
40
46
|
$ microlink markdown https://example.com
|
|
41
47
|
$ microlink screenshot https://example.com --fullPage
|
|
42
48
|
$ microlink logo https://github.com --square
|
package/bin/index.js
CHANGED
|
@@ -1,13 +1,219 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
+
const { styleText } = require('node:util')
|
|
4
5
|
const { readFileSync } = require('fs')
|
|
5
6
|
const path = require('path')
|
|
6
|
-
const jsome = require('jsome')
|
|
7
7
|
const mri = require('mri')
|
|
8
8
|
|
|
9
9
|
const create = require('../src')
|
|
10
10
|
|
|
11
|
+
const SHOW_CURSOR = '\u001b[?25h'
|
|
12
|
+
const HIDE_CURSOR = '\u001b[?25l'
|
|
13
|
+
const CLEAR_LINE = '\r\u001b[K'
|
|
14
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
15
|
+
|
|
16
|
+
const gray = str => styleText('gray', str)
|
|
17
|
+
const white = str => styleText('white', str)
|
|
18
|
+
const green = str => styleText('green', str)
|
|
19
|
+
const red = str => styleText('red', str)
|
|
20
|
+
const label = (text, color) =>
|
|
21
|
+
styleText(['inverse', 'bold', color], ` ${text.toUpperCase()} `)
|
|
22
|
+
const keyValue = (key, value) => key + ' ' + gray(value)
|
|
23
|
+
|
|
24
|
+
const prettyMs = ms => {
|
|
25
|
+
if (!Number.isFinite(ms)) return 'unknown'
|
|
26
|
+
const sign = ms < 0 ? '-' : ''
|
|
27
|
+
let n = Math.abs(ms)
|
|
28
|
+
if (n < 1000) return `${sign}${Math.round(n)}ms`
|
|
29
|
+
n /= 1000
|
|
30
|
+
if (n < 60) return `${sign}${n.toFixed(1).replace(/\.0$/, '')}s`
|
|
31
|
+
const hours = Math.floor(n / 3600)
|
|
32
|
+
n %= 3600
|
|
33
|
+
const mins = Math.floor(n / 60)
|
|
34
|
+
const secs = (n % 60).toFixed(1).replace(/\.0$/, '')
|
|
35
|
+
if (hours) return `${sign}${hours}h ${mins}m ${secs}s`
|
|
36
|
+
return secs === '0' ? `${sign}${mins}m` : `${sign}${mins}m ${secs}s`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const prettyBytes = n => {
|
|
40
|
+
if (!Number.isFinite(n) || n < 1000) return `${Math.round(n || 0)} B`
|
|
41
|
+
if (n < 1e6) {
|
|
42
|
+
const val = n / 1000
|
|
43
|
+
return `${
|
|
44
|
+
val >= 100 ? Math.round(val) : val.toFixed(1).replace(/\.0$/, '')
|
|
45
|
+
} kB`
|
|
46
|
+
}
|
|
47
|
+
return `${(n / 1e6).toFixed(1).replace(/\.0$/, '')} MB`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const toPlainHeaders = headers => {
|
|
51
|
+
if (!headers) return {}
|
|
52
|
+
if (typeof headers.entries === 'function') {
|
|
53
|
+
return Object.fromEntries(headers.entries())
|
|
54
|
+
}
|
|
55
|
+
return headers
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const humanizeApiKey = apiKey => `${String(apiKey).slice(0, 5)}…`
|
|
59
|
+
|
|
60
|
+
const quote = str =>
|
|
61
|
+
gray('"') + white(JSON.stringify(str).slice(1, -1)) + gray('"')
|
|
62
|
+
|
|
63
|
+
const printPretty = (value, indent = 0) => {
|
|
64
|
+
if (value === null) return white('null')
|
|
65
|
+
if (typeof value === 'string') return quote(value)
|
|
66
|
+
if (typeof value !== 'object') return white(String(value))
|
|
67
|
+
|
|
68
|
+
const isArray = Array.isArray(value)
|
|
69
|
+
const keys = isArray ? value : Object.keys(value)
|
|
70
|
+
if (keys.length === 0) return gray(isArray ? '[]' : '{}')
|
|
71
|
+
|
|
72
|
+
const pad = ' '.repeat(indent)
|
|
73
|
+
const inner = ' '.repeat(indent + 1)
|
|
74
|
+
const open = gray(isArray ? '[' : '{')
|
|
75
|
+
const close = gray(isArray ? ']' : '}')
|
|
76
|
+
const lines = keys.map(key => {
|
|
77
|
+
if (isArray) return inner + printPretty(key, indent + 1)
|
|
78
|
+
const name = /^[A-Za-z_$][\w$]*$/.test(key) ? white(key) : quote(key)
|
|
79
|
+
return inner + name + gray(':') + ' ' + printPretty(value[key], indent + 1)
|
|
80
|
+
})
|
|
81
|
+
return open + '\n' + lines.join(gray(',') + '\n') + '\n' + pad + close
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const printJson = payload => {
|
|
85
|
+
console.log(
|
|
86
|
+
process.stdout.hasColors?.()
|
|
87
|
+
? printPretty(payload)
|
|
88
|
+
: JSON.stringify(payload, null, 2)
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const tracePayload = ({
|
|
93
|
+
requestUrl,
|
|
94
|
+
requestOptions = {},
|
|
95
|
+
response,
|
|
96
|
+
full = false
|
|
97
|
+
}) => {
|
|
98
|
+
const rest = { ...requestOptions }
|
|
99
|
+
delete rest.responseType
|
|
100
|
+
const headers = { ...rest.headers }
|
|
101
|
+
if (!full && headers['x-api-key']) {
|
|
102
|
+
headers['x-api-key'] = humanizeApiKey(headers['x-api-key'])
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
request: { url: requestUrl, ...rest, headers },
|
|
106
|
+
response: {
|
|
107
|
+
...response,
|
|
108
|
+
headers: toPlainHeaders(response?.headers)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const shouldSpin = () =>
|
|
114
|
+
!process.env.NO_COLOR &&
|
|
115
|
+
process.env.FORCE_COLOR !== '0' &&
|
|
116
|
+
Boolean(process.stdout?.hasColors?.())
|
|
117
|
+
|
|
118
|
+
const spinner = () => {
|
|
119
|
+
const now = Date.now()
|
|
120
|
+
let i = 0
|
|
121
|
+
let timer
|
|
122
|
+
const draw = () => {
|
|
123
|
+
process.stderr.write(
|
|
124
|
+
`${CLEAR_LINE}${FRAMES[i++ % FRAMES.length]} ${prettyMs(
|
|
125
|
+
Date.now() - now
|
|
126
|
+
)}`
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
start () {
|
|
131
|
+
process.stderr.write(HIDE_CURSOR)
|
|
132
|
+
draw()
|
|
133
|
+
process.on('SIGINT', () => {
|
|
134
|
+
process.stderr.write(CLEAR_LINE + SHOW_CURSOR)
|
|
135
|
+
process.exit(130)
|
|
136
|
+
})
|
|
137
|
+
timer = setInterval(draw, 50)
|
|
138
|
+
},
|
|
139
|
+
stop () {
|
|
140
|
+
clearInterval(timer)
|
|
141
|
+
process.stderr.write(CLEAR_LINE + SHOW_CURSOR)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const printFooter = ({ duration, response }) => {
|
|
147
|
+
const headers = toPlainHeaders(response?.headers)
|
|
148
|
+
const time = prettyMs(duration)
|
|
149
|
+
const size = Number(headers['content-length']) || 0
|
|
150
|
+
const serverTiming = headers['server-timing']
|
|
151
|
+
const id = headers['x-request-id']
|
|
152
|
+
const edgeCacheStatus = headers['cf-cache-status']
|
|
153
|
+
const unifiedCacheStatus = headers['x-cache-status']
|
|
154
|
+
const cacheStatus =
|
|
155
|
+
unifiedCacheStatus === 'MISS' && edgeCacheStatus === 'HIT'
|
|
156
|
+
? edgeCacheStatus
|
|
157
|
+
: unifiedCacheStatus
|
|
158
|
+
const timestamp = Number(headers['x-timestamp'])
|
|
159
|
+
const ttl = Number(headers['x-cache-ttl'])
|
|
160
|
+
const expires = timestamp + ttl - Date.now()
|
|
161
|
+
const expiredAt =
|
|
162
|
+
cacheStatus === 'HIT' && Number.isFinite(expires)
|
|
163
|
+
? `(${prettyMs(expires)})`
|
|
164
|
+
: ''
|
|
165
|
+
const fetchMode = headers['x-fetch-mode']
|
|
166
|
+
const fetchTime = fetchMode && `(${headers['x-fetch-time']})`
|
|
167
|
+
const uri = response?.url
|
|
168
|
+
|
|
169
|
+
if (process.stdout.isTTY) console.error()
|
|
170
|
+
console.error(
|
|
171
|
+
label('success', 'green'),
|
|
172
|
+
gray(`${prettyBytes(size)} in ${time}`)
|
|
173
|
+
)
|
|
174
|
+
console.error()
|
|
175
|
+
|
|
176
|
+
if (serverTiming) {
|
|
177
|
+
console.error(' ', keyValue(green('timing'), serverTiming))
|
|
178
|
+
}
|
|
179
|
+
if (cacheStatus) {
|
|
180
|
+
console.error(
|
|
181
|
+
' ',
|
|
182
|
+
keyValue(green('cache'), `${cacheStatus} ${gray(expiredAt)}`.trim())
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
if (fetchMode) {
|
|
186
|
+
console.error(
|
|
187
|
+
' ',
|
|
188
|
+
keyValue(green('mode'), `${fetchMode} ${gray(fetchTime)}`.trim())
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
if (uri) console.error(' ', keyValue(green('uri'), uri))
|
|
192
|
+
if (id) console.error(' ', keyValue(green('id'), id))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const printFail = error => {
|
|
196
|
+
if (process.stdout.isTTY) console.error()
|
|
197
|
+
console.error(
|
|
198
|
+
label(error.status || 'fail', 'red'),
|
|
199
|
+
gray(String(error.message).replace(`${error.code}, `, ''))
|
|
200
|
+
)
|
|
201
|
+
console.error()
|
|
202
|
+
const id = error.headers?.['x-request-id']
|
|
203
|
+
if (id) console.error(' ', keyValue(red('id'), id))
|
|
204
|
+
if (error.url) console.error(' ', keyValue(red('uri'), error.url))
|
|
205
|
+
if (error.code) {
|
|
206
|
+
console.error(
|
|
207
|
+
' ',
|
|
208
|
+
keyValue(
|
|
209
|
+
red('code'),
|
|
210
|
+
`${error.code}${error.statusCode ? ` (${error.statusCode})` : ''}`
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
if (error.more) console.error(' ', keyValue(red('more'), error.more))
|
|
215
|
+
}
|
|
216
|
+
|
|
11
217
|
const showHelp = () => {
|
|
12
218
|
console.log(readFileSync(path.join(__dirname, 'help.txt'), 'utf8'))
|
|
13
219
|
process.exit(0)
|
|
@@ -27,10 +233,11 @@ const parseHeaders = input => {
|
|
|
27
233
|
|
|
28
234
|
const argv = mri(process.argv.slice(2), {
|
|
29
235
|
alias: { H: 'header' },
|
|
236
|
+
boolean: ['trace', 'trace-full'],
|
|
30
237
|
string: ['header', 'api-key', 'data', 'file']
|
|
31
238
|
})
|
|
32
239
|
|
|
33
|
-
|
|
240
|
+
let {
|
|
34
241
|
_: [command, target],
|
|
35
242
|
header,
|
|
36
243
|
help,
|
|
@@ -38,18 +245,35 @@ const {
|
|
|
38
245
|
file,
|
|
39
246
|
'api-key': apiKeyFlag,
|
|
40
247
|
apiKey: apiKeyCamel,
|
|
248
|
+
trace,
|
|
249
|
+
'trace-full': traceFull,
|
|
41
250
|
...flags
|
|
42
251
|
} = argv
|
|
43
252
|
|
|
253
|
+
const isTrace = trace || traceFull
|
|
254
|
+
|
|
44
255
|
if (help || !command) showHelp()
|
|
45
256
|
|
|
46
257
|
const apiKey = apiKeyFlag || apiKeyCamel || process.env.MICROLINK_API_KEY
|
|
47
258
|
const client = create(apiKey ? { apiKey } : {})
|
|
48
259
|
|
|
49
260
|
if (typeof client[command] !== 'function') {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
261
|
+
if (!target && URL.canParse(command)) {
|
|
262
|
+
target = command
|
|
263
|
+
command = 'metadata'
|
|
264
|
+
} else {
|
|
265
|
+
console.error(
|
|
266
|
+
`Unknown command \`${command}\`. Run \`microlink --help\` to see the available commands.`
|
|
267
|
+
)
|
|
268
|
+
process.exit(1)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (
|
|
273
|
+
isTrace &&
|
|
274
|
+
(command === 'search' || command === 'function' || command === 'run')
|
|
275
|
+
) {
|
|
276
|
+
console.error(`\`--trace\` is not supported for \`${command}\`.`)
|
|
53
277
|
process.exit(1)
|
|
54
278
|
}
|
|
55
279
|
|
|
@@ -68,14 +292,23 @@ const invoke = () => {
|
|
|
68
292
|
return client[command](target, options)
|
|
69
293
|
}
|
|
70
294
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
295
|
+
const spin = !isTrace && shouldSpin() ? spinner() : null
|
|
296
|
+
|
|
297
|
+
;(async () => {
|
|
298
|
+
spin?.start()
|
|
299
|
+
const started = Date.now()
|
|
300
|
+
try {
|
|
301
|
+
const result = await invoke()
|
|
302
|
+
const duration = Date.now() - started
|
|
303
|
+
spin?.stop()
|
|
304
|
+
if (isTrace) printJson(tracePayload({ ...client.last, full: traceFull }))
|
|
305
|
+
else if (typeof result === 'string') console.log(result)
|
|
306
|
+
else printJson({ status: 'success', data: result })
|
|
307
|
+
if (!isTrace) printFooter({ duration, response: client.last.response })
|
|
76
308
|
process.exit(0)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
|
|
309
|
+
} catch (error) {
|
|
310
|
+
spin?.stop()
|
|
311
|
+
printFail(error)
|
|
80
312
|
process.exit(1)
|
|
81
|
-
}
|
|
313
|
+
}
|
|
314
|
+
})()
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "microlink.io",
|
|
3
3
|
"description": "The Microlink API organized into products, each returning a direct result",
|
|
4
4
|
"homepage": "https://github.com/microlinkhq/microlink",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.1.1",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
7
7
|
"main": "./src/index.js",
|
|
8
8
|
"exports": {
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"@microlink/function": "0.3.0",
|
|
47
47
|
"@microlink/google": "1.0.6",
|
|
48
48
|
"@microlink/mql": "0.18.0",
|
|
49
|
-
"jsome": "~2.5.0",
|
|
50
49
|
"mri": "~1.2.0"
|
|
51
50
|
},
|
|
52
51
|
"devDependencies": {
|
|
@@ -77,5 +76,5 @@
|
|
|
77
76
|
},
|
|
78
77
|
"directory": "test"
|
|
79
78
|
},
|
|
80
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "a12fcd2a7bc918e741a71b50fd2cd93876f4ca8f"
|
|
81
80
|
}
|
package/src/index.js
CHANGED
|
@@ -17,7 +17,8 @@ const SCREENSHOT_KEYS = [
|
|
|
17
17
|
'optimizeForSpeed',
|
|
18
18
|
'codeScheme',
|
|
19
19
|
'animated',
|
|
20
|
-
'palette'
|
|
20
|
+
'palette',
|
|
21
|
+
'quality'
|
|
21
22
|
]
|
|
22
23
|
|
|
23
24
|
const PDF_KEYS = [
|
|
@@ -40,6 +41,17 @@ const LIGHTHOUSE_KEYS = ['onlyCategories', 'onlyAudits', 'skipAudits', 'output']
|
|
|
40
41
|
const isEmpty = obj => Object.keys(obj).length === 0
|
|
41
42
|
|
|
42
43
|
const create = (ctx = {}) => {
|
|
44
|
+
const last = {}
|
|
45
|
+
const request = (...args) => {
|
|
46
|
+
const [requestUrl, requestOptions] = mql.getApiUrl(...args)
|
|
47
|
+
last.requestUrl = requestUrl
|
|
48
|
+
last.requestOptions = requestOptions
|
|
49
|
+
return mql(...args).then(result => {
|
|
50
|
+
last.response = result.response
|
|
51
|
+
return result
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
/**
|
|
44
56
|
* Split the single options bag into the three destinations mql has:
|
|
45
57
|
* `got.headers` (HTTP layer, 3rd arg), `sub` (capability nested keys)
|
|
@@ -60,7 +72,7 @@ const create = (ctx = {}) => {
|
|
|
60
72
|
|
|
61
73
|
const content = field => (url, options) => {
|
|
62
74
|
const { top, sub, got } = route(options, CONTENT_KEYS)
|
|
63
|
-
return
|
|
75
|
+
return request(
|
|
64
76
|
url,
|
|
65
77
|
{ ...top, meta: false, data: { [field]: { attr: field, ...sub } } },
|
|
66
78
|
got
|
|
@@ -69,7 +81,7 @@ const create = (ctx = {}) => {
|
|
|
69
81
|
|
|
70
82
|
const collection = (field, rule) => (url, options) => {
|
|
71
83
|
const { top, sub, got } = route(options, COLLECTION_KEYS)
|
|
72
|
-
return
|
|
84
|
+
return request(
|
|
73
85
|
url,
|
|
74
86
|
{ ...top, meta: false, data: { [field]: { ...rule, ...sub } } },
|
|
75
87
|
got
|
|
@@ -78,7 +90,7 @@ const create = (ctx = {}) => {
|
|
|
78
90
|
|
|
79
91
|
const capability = (field, nested) => (url, options) => {
|
|
80
92
|
const { top, sub, got } = route(options, nested)
|
|
81
|
-
return
|
|
93
|
+
return request(
|
|
82
94
|
url,
|
|
83
95
|
{ ...top, meta: false, [field]: isEmpty(sub) ? true : sub },
|
|
84
96
|
got
|
|
@@ -88,7 +100,7 @@ const create = (ctx = {}) => {
|
|
|
88
100
|
/* Primary media detection (`data.video` / `data.audio`). */
|
|
89
101
|
const media = field => (url, options) => {
|
|
90
102
|
const { top, got } = route(options)
|
|
91
|
-
return
|
|
103
|
+
return request(url, { ...top, meta: false, [field]: true }, got).then(
|
|
92
104
|
({ data }) => data[field]
|
|
93
105
|
)
|
|
94
106
|
}
|
|
@@ -100,14 +112,14 @@ const create = (ctx = {}) => {
|
|
|
100
112
|
return fn(code, top, got)(url)
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
|
|
115
|
+
const client = {
|
|
104
116
|
metadata: (url, options) => {
|
|
105
117
|
const { top, got } = route(options)
|
|
106
|
-
return
|
|
118
|
+
return request(url, top, got).then(({ data }) => data)
|
|
107
119
|
},
|
|
108
120
|
logo: (url, options) => {
|
|
109
121
|
const { top, sub, got } = route(options, LOGO_KEYS)
|
|
110
|
-
return
|
|
122
|
+
return request(
|
|
111
123
|
url,
|
|
112
124
|
{ ...top, meta: isEmpty(sub) ? true : { logo: sub } },
|
|
113
125
|
got
|
|
@@ -141,7 +153,7 @@ const create = (ctx = {}) => {
|
|
|
141
153
|
}),
|
|
142
154
|
extract: (url, rules, options) => {
|
|
143
155
|
const { top, got } = route(options)
|
|
144
|
-
return
|
|
156
|
+
return request(url, { ...top, meta: false, data: rules }, got).then(
|
|
145
157
|
({ data }) => data
|
|
146
158
|
)
|
|
147
159
|
},
|
|
@@ -149,7 +161,7 @@ const create = (ctx = {}) => {
|
|
|
149
161
|
pdf: capability('pdf', PDF_KEYS),
|
|
150
162
|
embed: (url, options) => {
|
|
151
163
|
const { top, sub, got } = route(options, EMBED_KEYS)
|
|
152
|
-
return
|
|
164
|
+
return request(
|
|
153
165
|
url,
|
|
154
166
|
{ ...top, meta: false, iframe: isEmpty(sub) ? true : sub },
|
|
155
167
|
got
|
|
@@ -157,7 +169,7 @@ const create = (ctx = {}) => {
|
|
|
157
169
|
},
|
|
158
170
|
technologies: (url, options) => {
|
|
159
171
|
const { top, got } = route(options)
|
|
160
|
-
return
|
|
172
|
+
return request(
|
|
161
173
|
url,
|
|
162
174
|
{
|
|
163
175
|
...top,
|
|
@@ -169,7 +181,7 @@ const create = (ctx = {}) => {
|
|
|
169
181
|
},
|
|
170
182
|
lighthouse: (url, options) => {
|
|
171
183
|
const { top, sub, got } = route(options, LIGHTHOUSE_KEYS)
|
|
172
|
-
return
|
|
184
|
+
return request(
|
|
173
185
|
url,
|
|
174
186
|
{
|
|
175
187
|
...top,
|
|
@@ -189,6 +201,9 @@ const create = (ctx = {}) => {
|
|
|
189
201
|
function: run,
|
|
190
202
|
run
|
|
191
203
|
}
|
|
204
|
+
|
|
205
|
+
Object.defineProperty(client, 'last', { value: last })
|
|
206
|
+
return client
|
|
192
207
|
}
|
|
193
208
|
|
|
194
209
|
module.exports = create
|