mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-05 06:40:56 -06:00
212 lines
4.8 KiB
JavaScript
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,
|
|
}
|