Files
cypress/docs/lib/url_generator.js

212 lines
4.8 KiB
JavaScript

const _ = require('lodash')
const fs = require('hexo-fs')
const url = require('url')
const path = require('path')
const cheerio = require('cheerio')
const Promise = require('bluebird')
const request = require('request-promise')
const errors = require('request-promise/errors')
const startsWithHttpRe = /^http/
const everythingAfterHashRe = /(#.+)/
// cache validations
const cache = {}
function isExternalHref (str) {
return startsWithHttpRe.test(str)
}
function stripHash (str) {
return str.replace(everythingAfterHashRe, '')
}
function extractHash (str) {
const matches = everythingAfterHashRe.exec(str)
// return the hash match or empty string
return (matches && matches[0]) || ''
}
function assertHashIsPresent (descriptor, source, hash, html) {
// verify that the hash is present on this page
const $ = cheerio.load(html)
// hash starts with a '#hash'
// or href starts with '#hash'
if ($(hash).length || $(`a[href=${hash}]`).length) {
// found it, we good!
return
}
// we didnt find anything, so throw the errror
const truncated = _.truncate(html, { length: 200 }) || '""'
// if we dont have a hash
throw new Error(`Constructing {% url %} tag helper failed
> The source file was: ${source}
> You referenced a hash that does not exist at: ${descriptor}
> Expected to find an element matching the id: ${hash} or href: ${hash}
> The HTML response body was:
${truncated}
`)
}
function validateExternalUrl (href, source) {
const { hash } = url.parse(href)
return request(href)
.then((html) => {
// bail if we dont have a hash
if (!hash) {
return
}
assertHashIsPresent(href, source, hash, html)
})
.catch(errors.StatusCodeError, (err) => {
err.message = `Request to: ${href} failed. (Status Code ${err.statusCode})`
throw err
})
}
function normalizeNestedPaths (data) {
// takes the data, iterates through it
// and modifies all of the values with a fully
// qualified path to the html file
function flatten (obj, memo, parents) {
return _.reduce(obj, (memo, value, key) => {
if (_.isObject(value)) {
return flatten(value, memo, parents.concat(key))
}
memo[key] = parents.concat(value).join("/")
return memo
}, memo)
}
function expand (obj, parents) {
return _.mapValues(obj, (value, key) => {
if (_.isObject(value)) {
return expand(value, parents.concat(key))
}
return parents.concat(value).join("/")
})
}
// return
return {
expanded: expand(data, []),
flattened: flatten(data, {}, []),
}
}
function findFileBySource (sidebar, href) {
const { expanded, flattened } = normalizeNestedPaths(sidebar)
function property () {
// drill into the original sidebar object
return _.get(expanded, href.split('/'))
}
// find the path directly from the sidebar
// if provided, or go search for it
return flattened[href] || property()
}
function getLocalFile (sidebar, href) {
// get the resolve path to the file
// cypress-101 -> guides/core-concepts/cypress-101.html
const pathToFile = findFileBySource(sidebar, href)
if (pathToFile) {
// ensure this physically exists on disk
// inside of './source' folder
return fs.readFile(
path.resolve('source', pathToFile.replace('.html', '.md'))
).then((str) => {
// return an array with
// path to file, and str bytes
return [pathToFile, str]
})
}
return Promise.reject(
new Error(`Could not find a valid doc file in the sidebar.yml for: ${href}`)
)
}
function validateLocalFile (sidebar, href, source, render) {
const hash = extractHash(href)
return getLocalFile(sidebar, stripHash(href))
.spread((pathToFile, str) => {
if (hash) {
// if we have a hash then render
// the markdown contents so we can
// ensure it has the hash present!
return render(str)
.then((html) => {
assertHashIsPresent(pathToFile, source, hash, html)
return pathToFile
})
}
return pathToFile
})
.then((pathToFile) => {
return `/${pathToFile}${hash}`
})
}
function validateAndGetUrl (sidebar, href, source, render) {
// do we already have a cache for this href?
const cachedValue = cache[href]
// if we got it, return it!
if (cachedValue) {
return Promise.resolve(cachedValue)
}
if (isExternalHref(href)) {
// cache this now even though
// we haven't validated it yet
// because it will just fail later
cache[href] = href
return validateExternalUrl(href, source)
.return(href)
}
return validateLocalFile(sidebar, href, source, render)
.then((pathToFile) => {
// cache this once its been locally resolved
cache[href] = pathToFile
return pathToFile
})
}
module.exports = {
cache,
normalizeNestedPaths,
findFileBySource,
getLocalFile,
validateLocalFile,
validateAndGetUrl,
}