зеркало из https://github.com/github/docs.git
repo sync
This commit is contained in:
Коммит
fb25f98c92
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 39 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче