html-get 2.24.10 → 2.25.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +74 -50
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "html-get",
3
3
  "description": "Get the HTML from any website, fine-tuned for correction & speed",
4
4
  "homepage": "https://nicedoc.com/microlinkhq/html-get",
5
- "version": "2.24.10",
5
+ "version": "2.25.0",
6
6
  "types": "index.d.ts",
7
7
  "main": "src/index.js",
8
8
  "bin": {
package/src/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { parseUrl, isMediaUrl, isPdfUrl } = require('@metascraper/helpers')
3
+ const { parseUrl, isMediaUrl, isPdfUrl, memoizeOne } = require('@metascraper/helpers')
4
4
  const { readFile, writeFile } = require('fs/promises')
5
5
  const timeSpan = require('@kikobeats/time-span')()
6
6
  const debug = require('debug-logfmt')('html-get')
7
- const { execSync } = require('child_process')
7
+ const { execFileSync } = require('child_process')
8
8
  const PCancelable = require('p-cancelable')
9
9
  const { AbortError } = require('p-retry')
10
10
  const htmlEncode = require('html-encode')
@@ -28,15 +28,7 @@ const PDF_SIZE_TRESHOLD = 150 * 1024 // 150kb
28
28
  const fetch = PCancelable.fn(
29
29
  async (
30
30
  url,
31
- {
32
- getTemporalFile,
33
- mutool,
34
- pandoc,
35
- reflect = false,
36
- timeout = REQ_TIMEOUT,
37
- toEncode,
38
- ...opts
39
- },
31
+ { getTemporalFile, mutool, pandoc, reflect = false, timeout = REQ_TIMEOUT, toEncode, ...opts },
40
32
  onCancel
41
33
  ) => {
42
34
  const reqTimeout = reflect ? timeout / 2 : timeout
@@ -55,9 +47,7 @@ const fetch = PCancelable.fn(
55
47
  })
56
48
 
57
49
  const redirects = []
58
- req.on('redirect', res =>
59
- redirects.push({ statusCode: res.statusCode, url: res.url })
60
- )
50
+ req.on('redirect', res => redirects.push({ statusCode: res.statusCode, url: res.url }))
61
51
 
62
52
  try {
63
53
  const res = await req
@@ -65,8 +55,7 @@ const fetch = PCancelable.fn(
65
55
  const html = await (async () => {
66
56
  const contentType = getContentType(res.headers)
67
57
 
68
- const officeFormat =
69
- pandoc && getOfficeFormat({ contentType, url: [res.url, url] })
58
+ const officeFormat = pandoc && getOfficeFormat({ contentType, url: [res.url, url] })
70
59
 
71
60
  // a recognized office file never goes through mutool, even if the
72
61
  // response is mislabeled as application/pdf
@@ -86,7 +75,17 @@ const fetch = PCancelable.fn(
86
75
  if (officeFormat) {
87
76
  const file = getTemporalFile(res.url, officeFormat)
88
77
  await writeFile(file.path, res.body)
89
- return pandoc(officeFormat, file.path)
78
+ try {
79
+ const converted = await pandoc(officeFormat, file.path)
80
+ if (converted) return converted
81
+ debug('pandoc:empty', { url: res.url, format: officeFormat })
82
+ } catch (error) {
83
+ debug('pandoc:error', {
84
+ url: res.url,
85
+ format: officeFormat,
86
+ message: error.message || error
87
+ })
88
+ }
90
89
  }
91
90
 
92
91
  return contentType === 'text/html' || !isMediaUrl(url)
@@ -232,12 +231,7 @@ const isFetchMode = url => {
232
231
  }
233
232
 
234
233
  const defaultGetMode = (url, { prerender }) => {
235
- if (
236
- prerender === false ||
237
- isMediaUrl(url) ||
238
- isPdfUrl(url) ||
239
- isOfficeUrl(url)
240
- ) {
234
+ if (prerender === false || isMediaUrl(url) || isPdfUrl(url) || isOfficeUrl(url)) {
241
235
  return 'fetch'
242
236
  }
243
237
  if (prerender === true) return 'prerender'
@@ -246,41 +240,68 @@ const defaultGetMode = (url, { prerender }) => {
246
240
 
247
241
  const defaultGetTemporalFile = (input, ext) => {
248
242
  const hash = crypto.createHash('sha256').update(input).digest('hex')
249
- const filepath = path.join(
250
- os.tmpdir(),
251
- ext === undefined ? hash : `${hash}.${ext}`
252
- )
243
+ const filepath = path.join(os.tmpdir(), ext === undefined ? hash : `${hash}.${ext}`)
253
244
  return { path: filepath }
254
245
  }
255
246
 
256
- const defaultMutool = () =>
257
- (() => {
247
+ // Resolve a binary path once and cache it: html-get owns the pandoc/mutool
248
+ // lookup, so both the internal runners and the exposed accessors share a single
249
+ // `which` per binary for the whole process. execFileSync (no shell) is faster
250
+ // than execSync and free of any interpolation surface.
251
+ const whichCache = new Map()
252
+ const whichSync = bin => {
253
+ if (!whichCache.has(bin)) {
254
+ let resolved
258
255
  try {
259
- const mutoolPath = execSync('which mutool', {
256
+ resolved = execFileSync('which', [bin], {
260
257
  stdio: ['pipe', 'pipe', 'ignore']
261
258
  })
262
259
  .toString()
263
260
  .trim()
264
- return (...args) => $(`${mutoolPath} draw -q -F html ${args}`)
265
261
  } catch (_) {}
266
- })()
262
+ whichCache.set(bin, resolved)
263
+ }
264
+ return whichCache.get(bin)
265
+ }
267
266
 
268
- const defaultPandoc = () =>
269
- (() => {
270
- try {
271
- const pandocPath = execSync('which pandoc', {
267
+ // The default runners are wired as default parameters (evaluated on every
268
+ // call), so memoizeOne builds each runner once instead of re-running the pandoc
269
+ // `--list-input-formats` probe and rebuilding the closure per request.
270
+ const defaultMutool = memoizeOne(() => {
271
+ const mutoolPath = whichSync('mutool')
272
+ if (!mutoolPath) return
273
+ return (...args) => $(`${mutoolPath} draw -q -F html ${args}`)
274
+ })
275
+
276
+ const defaultPandoc = memoizeOne(() => {
277
+ try {
278
+ const pandocPath = whichSync('pandoc')
279
+ if (!pandocPath) return
280
+ // a broken/incompatible pandoc can throw here (or on the runner below); in
281
+ // either case conversion stays disabled instead of breaking every getHTML
282
+ // call through the default-parameter evaluation
283
+ const supported = new Set(
284
+ execFileSync(pandocPath, ['--list-input-formats'], {
272
285
  stdio: ['pipe', 'pipe', 'ignore']
273
286
  })
274
287
  .toString()
275
288
  .trim()
276
- return async (format, filepath) => {
277
- const { stdout } = await $(
278
- `${pandocPath} --from=${format} --to=html --standalone --embed-resources ${filepath}`
279
- )
280
- return stdout
281
- }
282
- } catch (_) {}
283
- })()
289
+ .split(/\s+/)
290
+ )
291
+ return async (format, filepath) => {
292
+ if (!supported.has(format)) return
293
+ // array form keeps `filepath` a single argv entry even if it has spaces
294
+ const { stdout } = await $(pandocPath, [
295
+ `--from=${format}`,
296
+ '--to=html',
297
+ '--standalone',
298
+ '--embed-resources',
299
+ filepath
300
+ ])
301
+ return stdout.trim() ? stdout : undefined
302
+ }
303
+ } catch (_) {}
304
+ })
284
305
 
285
306
  const getContent = PCancelable.fn(
286
307
  (
@@ -372,12 +393,7 @@ module.exports = PCancelable.fn(
372
393
 
373
394
  let shadowDOM = hasShadowDOM($)
374
395
 
375
- if (
376
- mode === 'fetch' &&
377
- getBrowserless &&
378
- shadowDOM &&
379
- prerender !== false
380
- ) {
396
+ if (mode === 'fetch' && getBrowserless && shadowDOM && prerender !== false) {
381
397
  debug('shadow DOM detected, retrying with prerender', { url: targetUrl })
382
398
  const prerenderPromise = getContent(targetUrl, 'prerender', {
383
399
  getBrowserless,
@@ -411,3 +427,11 @@ module.exports.PDF_SIZE_TRESHOLD = PDF_SIZE_TRESHOLD
411
427
  module.exports.isFetchMode = isFetchMode
412
428
  module.exports.getContent = getContent
413
429
  module.exports.defaultMutool = defaultMutool
430
+ module.exports.defaultPandoc = defaultPandoc
431
+
432
+ // html-get already resolves pandoc/mutool for office and PDF conversion; expose
433
+ // the cached paths so a consumer (e.g. the microlink api markdown pipeline)
434
+ // reuses them instead of running its own `which`. Each returns undefined when
435
+ // the binary is not installed. Lazy: nothing runs until first call.
436
+ module.exports.getPandocPath = () => whichSync('pandoc')
437
+ module.exports.getMutoolPath = () => whichSync('mutool')