This commit is contained in:
Octomerger Bot 2021-01-23 07:59:28 +10:00 коммит произвёл GitHub
Родитель dc45d6b268 6ae96188f2
Коммит fb25f98c92
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 198 добавлений и 350 удалений

2
.github/allowed-actions.js поставляемый
Просмотреть файл

@ -8,7 +8,7 @@ module.exports = [
'actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f', //actions/checkout@v2.3.4
'actions/github-script@626af12fe9a53dc2972b48385e7fe7dec79145c9', //actions/script@v3.0.0
'actions/labeler@5f867a63be70efff62b767459b009290364495eb', //actions/labeler@v2.2.0
'actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d', //actions/setup-node@v1.4.4
'actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e', //actions/setup-node@v2.1.4
'actions/setup-ruby@5f29a1cd8dfebf420691c4c9a0e832e2fae5a526', //actions/setup-ruby@v1.1.2
'actions/stale@af4072615903a8b031f986d25b1ae3bf45ec44d4', //actions/stale@v3.0.13
'crowdin/github-action@fd9429dd63d6c0f8a8cb4b93ad8076990bd6e688',

Просмотреть файл

@ -11,7 +11,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
- uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x
- name: cache node modules

2
.github/workflows/js-lint.yml поставляемый
Просмотреть файл

@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Setup node
uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x

55
.github/workflows/link-check-test.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,55 @@
name: Link Checker
on:
workflow_dispatch:
push:
jobs:
see_if_should_skip:
continue-on-error: true
runs-on: self-hosted
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@36feb0d8d062137530c2e00bd278d138fe191289
with:
cancel_others: 'false'
github_token: ${{ github.token }}
paths: '[".github/workflows/link-check-test.yml", "assets/**", "content/**", "data/**", "includes/**", "javascripts/**", "jest-puppeteer.config.js", "jest.config.js", "layouts/**", "lib/**", "middleware/**", "package-lock.json", "package.json", "server.js", "translations/**", "webpack.config.js"]'
build:
needs: see_if_should_skip
runs-on: self-hosted
steps:
# Each of these ifs needs to be repeated at each step to make sure the required check still runs
# Even if if doesn't do anything
- if: ${{ needs.see_if_should_skip.outputs.should_skip != 'true' }}
name: Checkout
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- if: ${{ needs.see_if_should_skip.outputs.should_skip != 'true' }}
name: Setup node
uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x
- if: ${{ needs.see_if_should_skip.outputs.should_skip != 'true' }}
name: Install
run: npm ci
## TODO
# - if: ${{ github.repository == 'github/docs-internal' && needs.see_if_should_skip.outputs.should_skip != 'true' }}
# name: Clone early access
# run: npm run heroku-postbuild
# env:
# DOCUBOT_REPO_PAT: ${{ secrets.DOCUBOT_REPO_PAT }}
# GIT_BRANCH: ${{ github.ref }}
- if: ${{ needs.see_if_should_skip.outputs.should_skip != 'true' }}
name: Build
run: npm run build
- if: ${{ needs.see_if_should_skip.outputs.should_skip != 'true' }}
name: Link check
run: npm run link-check

Просмотреть файл

@ -14,7 +14,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
- uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x
- name: cache node modules

Просмотреть файл

@ -20,7 +20,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
- uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x
- name: cache node modules

4
.github/workflows/test-translations.yml поставляемый
Просмотреть файл

@ -17,7 +17,7 @@ jobs:
ref: translations # check out the 'translations' branch
- name: Setup node
uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x
@ -55,7 +55,7 @@ jobs:
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Setup node
uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x

4
.github/workflows/test-windows.yml поставляемый
Просмотреть файл

@ -15,13 +15,13 @@ jobs:
strategy:
fail-fast: false
matrix:
test-group: [content, meta, rendering, routing, unit, links-and-images]
test-group: [content, meta, rendering, routing, unit]
steps:
- name: Check out repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Setup node
uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x

5
.github/workflows/test.yml поставляемый
Просмотреть файл

@ -36,8 +36,7 @@ jobs:
strategy:
fail-fast: false
matrix:
test-group:
[content, meta, rendering, routing, unit, links-and-images, graphql]
test-group: [content, meta, rendering, routing, unit, graphql]
steps:
# Each of these ifs needs to be repeated at each step to make sure the required check still runs
# Even if if doesn't do anything
@ -50,7 +49,7 @@ jobs:
- if: ${{ needs.see_if_should_skip.outputs.should_skip != 'true' }}
name: Setup node
uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x

2
.github/workflows/yml-lint.yml поставляемый
Просмотреть файл

@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Setup node
uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d
uses: actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e
with:
node-version: 14.x

Двоичные данные
assets/images/site/labtocat.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 39 KiB

Двоичные данные
assets/images/site/waldocat.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 36 KiB

Просмотреть файл

@ -5,9 +5,10 @@ permissions: 'Organization owners can manage the publication of {% data variable
product: '{% data reusables.gated-features.pages %}'
versions:
free-pro-team: '*'
enterprise-server: '>3.0'
enterprise-server: '>=3.0'
redirect_from:
- /github/setting-up-and-managing-organizations-and-teams/disabling-the-publication-of-github-pages-sites-for-your-organization
- /github/setting-up-and-managing-organizations-and-teams/disabling-publication-of-github-pages-sites-for-your-organization
---
{% if currentVersion == "free-pro-team@latest" %}

Просмотреть файл

@ -19,7 +19,7 @@
</div>
<div class="col-md-8 col-lg-6 mx-auto">
<img src="https://octodex.github.com/images/waldocat.png" alt="waldocat">
<img src="/assets/images/site/waldocat.png" alt="waldocat">
</div>
<div class="col-lg-12 mt-6">

Просмотреть файл

@ -16,7 +16,7 @@
<pre><code>{{ error.stack }}</code></pre>
{% endif %}
<img src="https://octodex.github.com/images/labtocat.png" alt="labtocat">
<img src="/assets/images/site/labtocat.png" alt="labtocat">
</article>
</div>

Просмотреть файл

@ -25,7 +25,6 @@ module.exports = async (req, res, next) => {
'data:',
'github.githubassets.com',
'github-images.s3.amazonaws.com',
'octodex.github.com',
'placehold.it',
'*.githubusercontent.com',
'github.com'

Просмотреть файл

@ -171,6 +171,9 @@
"prevent-pushes-to-main": "node script/prevent-pushes-to-main.js",
"pa11y-ci": "pa11y-ci",
"pa11y-test": "start-server-and-test browser-test-server 4001 pa11y-ci",
"link-check": "start-server-and-test link-check-server 4002 link-check-test",
"link-check-server": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en' PORT=4002 node server.js",
"link-check-test": "cross-env node script/check-internal-links.js",
"heroku-postbuild": "node script/early-access/clone-for-build.js && npm run build"
},
"engines": {

Просмотреть файл

@ -71,9 +71,16 @@ This script is run automatically when you run the server locally. It checks whet
---
### [`check-internal-links.js`](check-internal-links.js)
This script runs in CI via GitHub Action to check all *internal* links in English content, not including deprecated Enterprise Server content. This is different from script/check-english-links.js, which checks *all* links in the site, both internal and external, and is much slower.
---
### [`check-s3-images.js`](check-s3-images.js)
Run this script in your branch to check whether any images referenced in content are not in an expected S3 bucket. You will need to authenticate to S3 via `awssume` to use this script.
Run this script in your branch to check whether any images referenced in content are not in an expected S3 bucket. You will need to authenticate to S3 via `awssume` to use this script. Instructions for the one-time setup are at docs-content/doc-team-workflows/workflow-information-for-all-writers/setting-up-awssume-and-s3cmd.md
---
@ -89,6 +96,13 @@ Run this script in your branch to check whether any images referenced in content
---
### [`content-migrations/remove-html-comments-from-index-files.js`](content-migrations/remove-html-comments-from-index-files.js)
---
@ -120,6 +134,41 @@ This script finds and lists all the Heroku staging apps and deletes any leftover
---
### [`early-access/clone-for-build.js`](early-access/clone-for-build.js)
This script is run as a postbuild script during staging and deployments on Heroku. It clones a branch in the early-access repo that matches the current branch in the docs repo; if one can't be found, it clones the `main` branch.
---
### [`early-access/clone-locally`](early-access/clone-locally)
This script is run on a writer's machine to begin developing Early Access content locally.
---
### [`early-access/create-branch`](early-access/create-branch)
This script is run on a writer's machine to create an Early Access branch that matches the current docs-internal branch.
---
### [`early-access/symlink-from-local-repo.js`](early-access/symlink-from-local-repo.js)
This script is run on a writer's machine while developing Early Access content locally. You must pass the script the location of your local copy of the `github/docs-early-access` git repo as the first argument.
---
### [`early-access/update-data-and-image-paths.js`](early-access/update-data-and-image-paths.js)
This script is run on a writer's machine while developing Early Access content locally. It updates the data and image paths to either include `early-access` or remove it.
---
### [`enterprise-server-deprecations/archive-version.js`](enterprise-server-deprecations/archive-version.js)
Run this script during the Enterprise deprecation process to download static copies of all pages for the oldest supported Enterprise version. See the Enterprise deprecation issue template for instructions.
@ -193,6 +242,12 @@ Given: /github/getting-started-with-github/using-github Returns: /free-pro-team@
Given: /enterprise/admin/installation/upgrading-github-enterprise Returns: /enterprise-server@2.22/admin/installation/upgrading-github-enterprise
---
### [`graphql/build-changelog.js`](graphql/build-changelog.js)
---
@ -316,6 +371,13 @@ Run this script to manually purge the Fastly cache for all language variants of
---
### [`purge-redis-pages.js`](purge-redis-pages.js)
Run this script to manually purge the Redis rendered page cache. This will typically only be run by Heroku during the deployment process, as triggered via our Procfile's "release" phase configuration.
---
### [`reconcile-category-dirs-with-ids.js`](reconcile-category-dirs-with-ids.js)
An automated test checks for discrepancies between category directory names and slugified category titles as IDs.
@ -362,11 +424,11 @@ Examples:
reset a single translated file using a relative path: $ script/reset-translated-file.js translations/es-XL/content/actions/index.md
reset a single translated file using a full path: $ script/reset-translated-file.js /Users/z/git/github/docs/translations/es-XL/content/actions/index.md
reset a single translated file using a full path: $ script/reset-translated-file.js /Users/z/git/github/docs-internal/translations/es-XL/content/actions/index.md
reset all language variants of a single English file (using a relative path): $ script/reset-translated-file.js content/actions/index.md $ script/reset-translated-file.js data/ui.yml
reset all language variants of a single English file (using a full path): $ script/reset-translated-file.js /Users/z/git/github/docs/content/desktop/index.md $ script/reset-translated-file.js /Users/z/git/github/docs/data/ui.yml
reset all language variants of a single English file (using a full path): $ script/reset-translated-file.js /Users/z/git/github/docs-internal/content/desktop/index.md $ script/reset-translated-file.js /Users/z/git/github/docs-internal/data/ui.yml
---
@ -422,14 +484,14 @@ Starts the local development server with all of the available languages enabled.
### [`standardize-frontmatter-order.js`](standardize-frontmatter-order.js)
Run this script to standardize frontmatter fields in all content files.
Run this script to standardize frontmatter fields in all content files, per the order: - title - intro - product callout - productVersion - map topic status - hidden status - layout - redirect
---
### [`sync-algolia-search-indices.js`](sync-algolia-search-indices.js)
### [`sync-search-indices.js`](sync-search-indices.js)
This script is run automatically via GitHub Actions on every push to `master` to generate searchable data and upload it to our Algolia account. It can also be run manually. For more info see [contributing/search.md](contributing/search.md)
This script is run automatically via GitHub Actions on every push to `main` to generate searchable data. It can also be run manually. For more info see [contributing/search.md](contributing/search.md)
---
@ -443,15 +505,7 @@ List all the TODOs in our JavaScript files and stylesheets.
### [`update-enterprise-dates.js`](update-enterprise-dates.js)
Run this script during Enterprise releases and deprecations. It uses the GitHub API to get dates from `enterprise-releases` and updates `lib/enterprise-dates.json`. The help site uses this JSON to display dates at the top of some Enterprise versions.
This script requires that you have a GitHub Personal Access Token in a `.env` file. If you don't have a token, get one [here](https://github.com/settings/tokens/new?scopes=repo&description=docs-dev). If you don't have an `.env` file in your docs checkout, run this command in Terminal:
`cp .env.example .env`
Open the `.env` file in a text editor, and find the `GITHUB_TOKEN=` placeholder. Add your token after the equals sign.
Do not commit the `.env` file; just leave it in your checkout.
This script fetches data from https://github.com/github/enterprise-releases/blob/master/releases.json and updates `lib/enterprise-dates.json`, which the site uses for various functionality.
---
@ -482,3 +536,5 @@ This script is used by other scripts to update temporary AWS credentials and aut
Use this script to upload individual or batched asset files to a versioned S3 bucket. Run `upload-images-to-s3.js --help` for usage details.
---

52
script/check-internal-links.js Executable file
Просмотреть файл

@ -0,0 +1,52 @@
#!/usr/bin/env node
const linkinator = require('linkinator')
const checker = new linkinator.LinkChecker()
const { deprecated } = require('../lib/enterprise-server-releases')
// [start-readme]
//
// This script runs in CI via GitHub Action to check all *internal* links in English content,
// not including deprecated Enterprise Server content. This is different from script/check-english-links.js,
// which checks *all* links in the site, both internal and external, and is much slower.
//
// [end-readme]
const config = {
path: 'http://localhost:4002/en',
// Use concurrency = 10 to optimize for Actions
// See https://github.com/JustinBeckwith/linkinator/issues/135#issuecomment-623240879
concurrency: 10,
recurse: true,
linksToSkip: [
// Skip any link that is not an internal link
'^((?!http://localhost:4002/en).)*$',
// Skip dist files
'/dist/index.*',
// Skip deprecated Enterprise content
`enterprise(-server@|/)(${deprecated.join('|')})/?`
]
}
main()
async function main () {
const result = (await checker.check(config)).links
const brokenLinks = result
.filter(link => link.state === 'BROKEN')
.map(link => { delete link.failureDetails; return link })
// Exit successfully if no broken links!
if (!brokenLinks.length) {
console.log('All links are good!')
process.exit(0)
}
console.log('\n==============================')
console.log(`Found ${brokenLinks.length} total broken links: ${JSON.stringify([...brokenLinks], null, 2)}`)
console.log('==============================\n')
// Exit unsuccessfully if broken links are found.
process.exit(1)
}

Просмотреть файл

@ -1,269 +0,0 @@
const cheerio = require('cheerio')
const { union, uniq } = require('lodash')
const fs = require('fs')
const path = require('path')
const { getProductStringFromPath } = require('../../lib/path-utils')
const patterns = require('../../lib/patterns')
const { deprecated } = require('../../lib/enterprise-server-releases')
const rest = require('../../middleware/contextualizers/rest')
const graphql = require('../../middleware/contextualizers/graphql')
const contextualize = require('../../middleware/context')
const releaseNotes = require('../../middleware/contextualizers/enterprise-release-notes')
const versionSatisfiesRange = require('../../lib/version-satisfies-range')
class LinksChecker {
constructor (opts = { languageCode: 'en', internalHrefPrefixes: ['/', '#'] }) {
Object.assign(this, { ...opts })
// Some caching mechanism so we do not load pages unnecessarily,
// nor check links that have been checked
this.pageCache = new Map()
this.checkedLinksCache = new Set()
// stores images to check all at once in a Map:
// imageSrc => {
// "usedBy": [version:path, ...]
// }
this.imagesToCheck = new Map()
// Stores broken images in a Map, formatted the same way as imagesToCheck
this.brokenImages = new Map()
// Stores broken links in a Map in the format of:
// link => {
// linkedFrom: [ version:filePath, ... ]
// }, ...
this.brokenLinks = new Map()
// stores anchor links to check all at once in a Map:
// version:filePath => {
// '#anchor-link' : {
// linkedFrom: ['url1', 'url2']
// },
// '#anchor-link2': {...}
// }
this.anchorLinksToCheck = new Map()
// Stores broken anchors in a Map, formatted the same way as anchorLinksToCheck
this.brokenAnchors = new Map()
}
async setRenderedPageObj (pathCacheKey, context, reRender = false) {
if (this.pageCache.has(pathCacheKey) && !reRender) return
let pageHTML = await context.page.render(context)
// handle special pre-rendered snowflake
if (context.page.relativePath.endsWith('graphql/reference/objects.md')) {
pageHTML += context.graphql.prerenderedObjectsForCurrentVersion.html
}
const pageObj = cheerio.load(pageHTML, { xmlMode: true })
this.pageCache.set(pathCacheKey, pageObj)
}
async getRenderedPageObj (pathCacheKey, context) {
if (!this.pageCache.has(pathCacheKey)) {
if (context) {
await this.setRenderedPageObj(pathCacheKey, context)
} else {
console.error('cannot find pre-rendered page, and does not have enough context to render one.')
}
}
return this.pageCache.get(pathCacheKey)
}
addAnchorForLater (pagePath, anchor, linkedFrom) {
const anchorsInPath = this.anchorLinksToCheck.get(pagePath) || {}
const anchorLink = anchorsInPath[anchor] || { linkedFrom: [] }
anchorLink.linkedFrom = union(anchorLink.linkedFrom, [linkedFrom])
anchorsInPath[anchor] = anchorLink
this.anchorLinksToCheck.set(pagePath, anchorsInPath)
}
addImagesForLater (images, pagePath) {
uniq(images).forEach(imageSrc => {
const imageUsage = this.imagesToCheck.get(imageSrc) || { usedBy: [] }
imageUsage.usedBy = union(imageUsage.usedBy, [pagePath])
this.imagesToCheck.set(imageSrc, imageUsage)
})
}
async checkPage (context, checkExternalAnchors) {
const path = context.relativePath
const version = context.currentVersion
const pathCacheKey = `${version}:${path}`
const $ = await this.getRenderedPageObj(pathCacheKey, context)
const imageSrcs = $('img[src^="/assets"]').map((i, el) => $(el).attr('src')).toArray()
this.addImagesForLater(imageSrcs, pathCacheKey)
for (const href of this.internalHrefPrefixes) {
const internalLinks = $(`a[href^="${href}"]`).get()
for (const internalLink of internalLinks) {
const href = $(internalLink).attr('href')
let [link, anchor] = href.split('#')
// remove trailing slash
link = link.replace(patterns.trailingSlash, '$1')
// if it's an external link and has been checked before, skip
if (link && this.checkedLinksCache.has(link)) {
// if it's been determined this link is broken, add to the linkedFrom field
if (this.brokenLinks.has(link)) {
const brokenLink = this.brokenLinks.get(link)
brokenLink.linkedFrom = union(brokenLink.linkedFrom, [pathCacheKey])
this.brokenLinks.set(link, brokenLink)
}
if (!anchor) continue
}
// if it's an internal anchor (e.g., #foo), save for later
if (anchor && !link) {
// ignore anchors that are autogenerated from headings
if (anchor === $(internalLink).parent().attr('id')) continue
this.addAnchorForLater(pathCacheKey, anchor, 'same page')
continue
}
// ------ BEGIN ONEOFF EXCLUSIONS -------///
// skip GraphQL public schema paths (these are checked by separate tests)
if (link.startsWith('/public/') && link.endsWith('.graphql')) continue
// skip links that start with /assets/images, as these are not in the pages collection
// and /assets/images paths should be checked during the image check
if (link.startsWith('/assets/images')) continue
// skip rare hardcoded links to old GHE versions
// these paths will always be in the old versioned format
// example: /enterprise/11.10.340/admin/articles/upgrading-to-the-latest-release
const gheVersionInLink = link.match(patterns.getEnterpriseVersionNumber)
if (gheVersionInLink && deprecated.includes(gheVersionInLink[1])) continue
// ------ END ONEOFF EXCLUSIONS -------///
// look for linked page
const linkedPage = context.pages[link] || context.pages[context.redirects[link]]
this.checkedLinksCache.add(link)
if (!linkedPage) {
this.brokenLinks.set(link, { linkedFrom: [pathCacheKey] })
continue
}
// if we're not checking external anchors, we're done
if (!checkExternalAnchors) {
continue
}
// find the permalink for the current version
const linkedPagePermalink = linkedPage.permalinks.find(permalink => permalink.pageVersion === version)
if (linkedPagePermalink) {
const linkedPageContext = await buildPathContext(context, linkedPage, linkedPagePermalink)
if (anchor) {
await this.setRenderedPageObj(`${version}:${linkedPage.relativePath}`, linkedPageContext)
this.addAnchorForLater(`${version}:${linkedPage.relativePath}`, anchor, pathCacheKey)
}
}
}
}
}
async checkAnchors () {
for await (const [pathCacheKey, anchors] of this.anchorLinksToCheck) {
const $ = await this.getRenderedPageObj(pathCacheKey)
for (const anchorText in anchors) {
const matchingHeadings = $(`[id="${anchorText}"], [name="${anchorText}"]`)
if (matchingHeadings.length === 0) {
const brokenAnchorPath = this.brokenAnchors.get(pathCacheKey) || {}
brokenAnchorPath[anchorText] = anchors[anchorText]
this.brokenAnchors.set(pathCacheKey, brokenAnchorPath)
}
}
}
}
getBrokenLinks () {
return this.brokenLinks
}
async getBrokenAnchors () {
await this.checkAnchors()
return this.brokenAnchors
}
async getBrokenImages () {
for await (const [imageSrc, imageUsage] of this.imagesToCheck) {
try {
await fs.promises.access(path.join(process.cwd(), imageSrc))
} catch (e) {
this.brokenImages.set(imageSrc, imageUsage)
}
}
return this.brokenImages
}
}
// this function is async because the middleware functions are likely async
async function applyMiddleware (middleware, req) {
return middleware(req, null, () => {})
}
async function buildInitialContext () {
const req = {
path: '/en',
language: 'en',
query: {}
}
await applyMiddleware(contextualize, req)
return req.context
}
async function buildPathContext (initialContext, page, permalink) {
// Create a new object with path-specific properties.
// Note this is cherry-picking properties currently only needed by the middleware below;
// See middleware/context.js for the rest of the properties we are NOT refreshing per page.
// If we find this causes problems for link checking, we can call `contextualize` on
// every page. For now, this cherry-picking approach is intended to improve performance so
// we don't have to build the expensive `pages`, `redirects`, etc. data on every page we check.
const path = permalink.href
const pathContext = {
page,
currentVersion: permalink.pageVersion,
currentProduct: getProductStringFromPath(path),
relativePath: permalink.relativePath,
currentPath: permalink.href
}
// Combine it with the initial context object that has pages, redirects, etc.
const combinedContext = Object.assign({}, initialContext, pathContext)
// Create a new req object using the combined context
const req = {
path,
context: combinedContext,
language: 'en',
query: {}
}
// Pass the req to the contextualizing middlewares
await applyMiddleware(rest, req)
await applyMiddleware(graphql, req)
// Release notes are available on docs site starting with GHES 3.0
if (versionSatisfiesRange(permalink.pageVersion, '>=3.0')) {
await applyMiddleware(releaseNotes, req)
}
// Return the resulting context object with REST, GraphQL, and release notes data now attached
return req.context
}
module.exports = {
LinksChecker,
buildPathContext,
buildInitialContext
}

Просмотреть файл

@ -1,47 +0,0 @@
const { LinksChecker, buildInitialContext, buildPathContext } = require('../helpers/links-checker')
const { uniq } = require('lodash')
const languageCode = 'en'
// TODO set to true when we're ready to report and fix broken anchors
const checkExternalAnchors = false
describe('page rendering', () => {
jest.setTimeout(1000 * 1000)
const linksChecker = new LinksChecker()
beforeAll(async (done) => {
// fetch context.pages, context.redirects, etc.
// we only want to build these one time
const context = await buildInitialContext()
const englishPages = uniq(Object.values(context.pages))
.filter(page => page.languageCode === languageCode)
for (const page of englishPages) {
for (const permalink of page.permalinks) {
const pathContext = await buildPathContext(context, page, permalink)
await linksChecker.checkPage(pathContext, checkExternalAnchors)
}
}
done()
})
test('every page has image references that can be resolved', async () => {
const result = await linksChecker.getBrokenImages()
expect(result.size, `Found ${result.size} total broken images: ${JSON.stringify([...result], null, 2)}`).toBe(0)
})
// When ready to unskip this,
test.skip('every page has links with anchors that can be resolved', async () => {
const result = await linksChecker.getBrokenAnchors()
const numBrokenAnchors = [...result].reduce((accumulator, [path, anchors]) => accumulator + Object.keys(anchors).length, 0)
expect(numBrokenAnchors, `Found ${numBrokenAnchors} total broken anchors in ${result.size} pages: ${JSON.stringify([...result], null, 2)}`).toBe(0)
})
test('every page has links that can be resolved', () => {
const result = linksChecker.getBrokenLinks()
expect(result.size, `Found ${result.size} total broken links: ${JSON.stringify([...result], null, 2)}`).toBe(0)
})
})

Просмотреть файл

@ -52,7 +52,6 @@ describe('server', () => {
expect(csp.get('img-src').includes("'self'")).toBe(true)
expect(csp.get('img-src').includes('github-images.s3.amazonaws.com')).toBe(true)
expect(csp.get('img-src').includes('octodex.github.com')).toBe(true)
expect(csp.get('script-src').includes("'self'")).toBe(true)