From 18504871b967f304618e9becf493b8348ced34f0 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Mon, 23 May 2022 08:12:09 -0400 Subject: [PATCH] cache full rendering (#25424) * cache full rendering * still not working with gzip * progress progress progress * smaller * hacky progress * small fixes * wip * lock file * wip * wip * package-lock updates * wip * search DOM in lowercase * simplify * with instrument * improve test coverage * mutateCheeriobodyByRequest * fix * remove renderContentCacheByContex * disable render caching in sync-search * diables things in github/github link checker * gzip lru * tidying up * updated * correct tests * fix: move userLanguage to LanguagesContext * Revert "fix: move userLanguage to LanguagesContext" This reverts commit d7c05d958c71eaad496eb46764eb845d80b866ca. * contexts ftw * fixed rendering tests * oops for got new file * nits addressed Co-authored-by: Mike Surowiec --- .../check-broken-links-github-github.yml | 5 +- .github/workflows/sync-search-indices.yml | 2 + components/DefaultLayout.tsx | 1 + .../context/DotComAuthenticatedContext.tsx | 19 + components/context/LanguagesContext.tsx | 1 + components/context/MainContext.tsx | 4 - components/page-header/Header.tsx | 5 +- .../page-header/HeaderNotifications.tsx | 6 +- lib/get-theme.js | 2 - lib/page.js | 35 +- middleware/cache-full-rendering.js | 167 ++++ middleware/index.js | 5 + middleware/render-page.js | 2 +- next.config.js | 3 + package-lock.json | 722 ++++++------------ package.json | 1 + pages/_app.tsx | 26 +- script/search/sync.js | 4 + tests/helpers/script-data.js | 29 + tests/rendering/head.js | 11 +- tests/rendering/header.js | 36 +- tests/rendering/render-caching.js | 160 ++++ tests/rendering/signup-button.js | 26 +- 23 files changed, 700 insertions(+), 572 deletions(-) create mode 100644 components/context/DotComAuthenticatedContext.tsx create mode 100644 middleware/cache-full-rendering.js create mode 100644 tests/helpers/script-data.js create mode 100644 tests/rendering/render-caching.js diff --git a/.github/workflows/check-broken-links-github-github.yml b/.github/workflows/check-broken-links-github-github.yml index a0561737f78a..d7273d7b20c1 100644 --- a/.github/workflows/check-broken-links-github-github.yml +++ b/.github/workflows/check-broken-links-github-github.yml @@ -57,8 +57,11 @@ jobs: env: NODE_ENV: production PORT: 4000 + # Overload protection is on by default (when NODE_ENV==production) + # but it would help in this context. DISABLE_OVERLOAD_PROTECTION: true - DISABLE_RENDER_CACHING: true + # Render caching won't help when we visit every page exactly once. + DISABLE_RENDERING_CACHE: true run: | node server.mjs & diff --git a/.github/workflows/sync-search-indices.yml b/.github/workflows/sync-search-indices.yml index d2f323733580..51e0975f7414 100644 --- a/.github/workflows/sync-search-indices.yml +++ b/.github/workflows/sync-search-indices.yml @@ -92,6 +92,8 @@ jobs: # Because the overload protection runs in NODE_ENV==production # and it can break the sync-search. DISABLE_OVERLOAD_PROTECTION: true + # Render caching won't help when we visit every page exactly once. + DISABLE_RENDERING_CACHE: true run: npm run sync-search diff --git a/components/DefaultLayout.tsx b/components/DefaultLayout.tsx index 5b89132791fa..311503617695 100644 --- a/components/DefaultLayout.tsx +++ b/components/DefaultLayout.tsx @@ -27,6 +27,7 @@ export const DefaultLayout = (props: Props) => { const { t } = useTranslation(['errors', 'meta', 'scroll_button']) const router = useRouter() const metaDescription = page.introPlainText ? page.introPlainText : t('default_description') + return (
diff --git a/components/context/DotComAuthenticatedContext.tsx b/components/context/DotComAuthenticatedContext.tsx new file mode 100644 index 000000000000..fcdf071355a5 --- /dev/null +++ b/components/context/DotComAuthenticatedContext.tsx @@ -0,0 +1,19 @@ +import { createContext, useContext } from 'react' + +export type DotComAuthenticatedContextT = { + isDotComAuthenticated: boolean +} + +export const DotComAuthenticatedContext = createContext(null) + +export const useAuth = (): DotComAuthenticatedContextT => { + const context = useContext(DotComAuthenticatedContext) + + if (!context) { + throw new Error( + '"useAuthContext" may only be used inside "DotComAuthenticatedContext.Provider"' + ) + } + + return context +} diff --git a/components/context/LanguagesContext.tsx b/components/context/LanguagesContext.tsx index 748b59031a27..03f2abf18a06 100644 --- a/components/context/LanguagesContext.tsx +++ b/components/context/LanguagesContext.tsx @@ -10,6 +10,7 @@ type LanguageItem = { export type LanguagesContextT = { languages: Record + userLanguage: string } export const LanguagesContext = createContext(null) diff --git a/components/context/MainContext.tsx b/components/context/MainContext.tsx index 59755e479750..cb10230c1c49 100644 --- a/components/context/MainContext.tsx +++ b/components/context/MainContext.tsx @@ -93,7 +93,6 @@ export type MainContextT = { relativePath?: string enterpriseServerReleases: EnterpriseServerReleases currentPathWithoutLanguage: string - userLanguage: string allVersions: Record currentVersion?: string currentProductTree?: ProductTreeNode | null @@ -125,7 +124,6 @@ export type MainContextT = { status: number fullUrl: string - isDotComAuthenticated: boolean } export const getMainContext = (req: any, res: any): MainContextT => { @@ -181,7 +179,6 @@ export const getMainContext = (req: any, res: any): MainContextT => { 'supported', ]), enterpriseServerVersions: req.context.enterpriseServerVersions, - userLanguage: req.context.userLanguage || '', allVersions: req.context.allVersions, currentVersion: req.context.currentVersion, currentProductTree: req.context.currentProductTree @@ -192,7 +189,6 @@ export const getMainContext = (req: any, res: any): MainContextT => { nonEnterpriseDefaultVersion: req.context.nonEnterpriseDefaultVersion, status: res.statusCode, fullUrl: req.protocol + '://' + req.get('host') + req.originalUrl, - isDotComAuthenticated: Boolean(req.cookies.dotcom_user), } } diff --git a/components/page-header/Header.tsx b/components/page-header/Header.tsx index ddbdc2ede7c1..b0ca9a6bab00 100644 --- a/components/page-header/Header.tsx +++ b/components/page-header/Header.tsx @@ -6,6 +6,7 @@ import { useVersion } from 'components/hooks/useVersion' import { Link } from 'components/Link' import { useMainContext } from 'components/context/MainContext' +import { useAuth } from 'components/context/DotComAuthenticatedContext' import { LanguagePicker } from './LanguagePicker' import { HeaderNotifications } from 'components/page-header/HeaderNotifications' import { ProductPicker } from 'components/page-header/ProductPicker' @@ -17,7 +18,7 @@ import styles from './Header.module.scss' export const Header = () => { const router = useRouter() - const { isDotComAuthenticated, error } = useMainContext() + const { error } = useMainContext() const { currentVersion } = useVersion() const { t } = useTranslation(['header', 'homepage']) const [isMenuOpen, setIsMenuOpen] = useState( @@ -25,6 +26,8 @@ export const Header = () => { ) const [scroll, setScroll] = useState(false) + const { isDotComAuthenticated } = useAuth() + const signupCTAVisible = !isDotComAuthenticated && (currentVersion === 'free-pro-team@latest' || currentVersion === 'enterprise-cloud@latest') diff --git a/components/page-header/HeaderNotifications.tsx b/components/page-header/HeaderNotifications.tsx index 4b696bc5f61e..0c5dd4d5f033 100644 --- a/components/page-header/HeaderNotifications.tsx +++ b/components/page-header/HeaderNotifications.tsx @@ -21,9 +21,9 @@ type Notif = { export const HeaderNotifications = () => { const router = useRouter() const { currentVersion } = useVersion() - const { relativePath, allVersions, data, userLanguage, currentPathWithoutLanguage, page } = - useMainContext() - const { languages } = useLanguages() + const { relativePath, allVersions, data, currentPathWithoutLanguage, page } = useMainContext() + const { languages, userLanguage } = useLanguages() + const { t } = useTranslation('header') const translationNotices: Array = [] diff --git a/lib/get-theme.js b/lib/get-theme.js index 50a6e0d181c9..a4b9b964dcae 100644 --- a/lib/get-theme.js +++ b/lib/get-theme.js @@ -1,11 +1,9 @@ -// export const defaultCSSThemeProps = { export const defaultCSSTheme = { colorMode: 'auto', // light, dark, auto nightTheme: 'dark', dayTheme: 'light', } -// export const defaultComponentThemeProps = { export const defaultComponentTheme = { colorMode: 'auto', // day, night, auto nightTheme: 'dark', diff --git a/lib/page.js b/lib/page.js index eacb1ae9683a..1bc8f7b950cc 100644 --- a/lib/page.js +++ b/lib/page.js @@ -24,22 +24,6 @@ import { union } from 'lodash-es' // every single time, we turn it into a Set once. const productMapKeysAsSet = new Set(Object.keys(productMap)) -// Wrapper on renderContent() that caches the output depending on the -// `context` by extracting information about the page's current permalink -const _renderContentCache = new Map() - -function renderContentCacheByContext(prefix) { - return async function (template = '', context = {}, options = {}) { - const { currentPath } = context - const cacheKey = prefix + currentPath - - if (!_renderContentCache.has(cacheKey)) { - _renderContentCache.set(cacheKey, await renderContent(template, context, options)) - } - return _renderContentCache.get(cacheKey) - } -} - class Page { static async init(opts) { opts = await Page.read(opts) @@ -186,18 +170,18 @@ class Page { context.englishHeadings = englishHeadings } - this.intro = await renderContentCacheByContext('intro')(this.rawIntro, context) - this.introPlainText = await renderContentCacheByContext('rawIntro')(this.rawIntro, context, { + this.intro = await renderContent(this.rawIntro, context) + this.introPlainText = await renderContent(this.rawIntro, context, { textOnly: true, }) - this.title = await renderContentCacheByContext('rawTitle')(this.rawTitle, context, { + this.title = await renderContent(this.rawTitle, context, { textOnly: true, encodeEntities: true, }) - this.titlePlainText = await renderContentCacheByContext('titleText')(this.rawTitle, context, { + this.titlePlainText = await renderContent(this.rawTitle, context, { textOnly: true, }) - this.shortTitle = await renderContentCacheByContext('shortTitle')(this.shortTitle, context, { + this.shortTitle = await renderContent(this.shortTitle, context, { textOnly: true, encodeEntities: true, }) @@ -205,7 +189,7 @@ class Page { this.product_video = await renderContent(this.raw_product_video, context, { textOnly: true }) context.relativePath = this.relativePath - const html = await renderContentCacheByContext('markdown')(this.markdown, context) + const html = await renderContent(this.markdown, context) // Adding communityRedirect for Discussions, Sponsors, and Codespaces - request from Product if ( @@ -222,15 +206,12 @@ class Page { // product frontmatter may contain liquid if (this.rawProduct) { - this.product = await renderContentCacheByContext('product')(this.rawProduct, context) + this.product = await renderContent(this.rawProduct, context) } // permissions frontmatter may contain liquid if (this.rawPermissions) { - this.permissions = await renderContentCacheByContext('permissions')( - this.rawPermissions, - context - ) + this.permissions = await renderContent(this.rawPermissions, context) } // Learning tracks may contain Liquid and need to have versioning processed. diff --git a/middleware/cache-full-rendering.js b/middleware/cache-full-rendering.js new file mode 100644 index 000000000000..0a80b5b70a36 --- /dev/null +++ b/middleware/cache-full-rendering.js @@ -0,0 +1,167 @@ +import zlib from 'zlib' + +import cheerio from 'cheerio' +import QuickLRU from 'quick-lru' + +// This is what NextJS uses when it injects the JSON serialized +// in the ` + // + const primerData = $('script#__PRIMER_DATA__') + console.assert(primerData.length === 1, 'Not exactly 1') + const parsedPrimerData = JSON.parse(primerData.get()[0].children[0].data) + parsedPrimerData.resolvedServerColorMode = cssTheme.colorMode === 'dark' ? 'night' : 'day' + primerData.text(htmlEscapeJsonString(JSON.stringify(parsedPrimerData))) +} diff --git a/middleware/index.js b/middleware/index.js index 2767f8d4bbed..3c6a2754d39a 100644 --- a/middleware/index.js +++ b/middleware/index.js @@ -63,6 +63,7 @@ import assetPreprocessing from './asset-preprocessing.js' import archivedAssetRedirects from './archived-asset-redirects.js' import favicons from './favicons.js' import setStaticAssetCaching from './static-asset-caching.js' +import cacheFullRendering from './cache-full-rendering.js' import protect from './overload-protection.js' import fastHead from './fast-head.js' @@ -317,6 +318,10 @@ export default function (app) { // full page rendering. app.head('/*', fastHead) + // For performance, this is before contextualizers if, on a cache hit, + // we can't reuse a rendered response without having to contextualize. + app.get('/*', asyncMiddleware(instrument(cacheFullRendering, './cache-full-rendering'))) + // *** Preparation for render-page: contextualizers *** app.use(asyncMiddleware(instrument(releaseNotes, './contextualizers/release-notes'))) app.use(instrument(graphQL, './contextualizers/graphql')) diff --git a/middleware/render-page.js b/middleware/render-page.js index 36919a5ed8a4..f24a2cdf673f 100644 --- a/middleware/render-page.js +++ b/middleware/render-page.js @@ -67,7 +67,7 @@ export default async function renderPage(req, res, next) { // Just finish fast without all the details like Content-Length if (req.method === 'HEAD') { - return res.status(200).end() + return res.status(200).send('') } // Updating the Last-Modified header for substantive changes on a page for engineering diff --git a/next.config.js b/next.config.js index c2a44f89d8a0..9ab2fa2ff0b4 100644 --- a/next.config.js +++ b/next.config.js @@ -39,4 +39,7 @@ module.exports = { config.experiments.topLevelAwait = true return config }, + + // https://nextjs.org/docs/api-reference/next.config.js/compression + compress: false, } diff --git a/package-lock.json b/package-lock.json index 3337f44814a0..b65201b8f7be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "overload-protection": "^1.2.3", "parse5": "^6.0.1", "port-used": "^2.0.8", + "quick-lru": "6.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-markdown": "^8.0.0", @@ -4100,48 +4101,6 @@ "once": "^1.4.0" } }, - "node_modules/@octokit/request/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/@octokit/request/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/@octokit/request/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/@octokit/request/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/@octokit/rest": { "version": "18.12.0", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", @@ -4786,9 +4745,9 @@ "devOptional": true }, "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "optional": true, "dependencies": { "@types/node": "*" @@ -5274,7 +5233,7 @@ "node_modules/array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "optional": true, "engines": { "node": ">=0.10.0" @@ -6420,7 +6379,7 @@ "node_modules/bfj": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/bfj/-/bfj-4.2.4.tgz", - "integrity": "sha1-hfeyNoPCr9wVhgOEotHD+sgO0zo=", + "integrity": "sha512-+c08z3TYqv4dy9b0MAchQsxYlzX9D2asHWW4VhO4ZFTnK7v9ps6iNhEQLqJyEZS6x9G0pgOCk/L7B9E4kp8glQ==", "optional": true, "dependencies": { "check-types": "^7.3.0", @@ -6467,7 +6426,7 @@ "node_modules/bmp-js": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM=", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", "optional": true }, "node_modules/bn.js": { @@ -6792,7 +6751,7 @@ "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "optional": true, "engines": { "node": "*" @@ -6801,7 +6760,7 @@ "node_modules/buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", "optional": true, "engines": { "node": ">=0.4.0" @@ -6928,6 +6887,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelcase-keys/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelcase-keys/node_modules/type-fest": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", @@ -8745,16 +8716,6 @@ "node": ">= 0.8.0" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/escodegen/node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -10221,9 +10182,9 @@ } }, "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "optional": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -10249,19 +10210,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "devOptional": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -10289,48 +10237,6 @@ "node": ">=10" } }, - "node_modules/gaxios/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/gaxios/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/gaxios/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/gaxios/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/gemoji": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/gemoji/-/gemoji-4.2.1.tgz", @@ -10419,12 +10325,12 @@ "dev": true }, "node_modules/gifwrap": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.2.tgz", - "integrity": "sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", + "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", "optional": true, "dependencies": { - "image-q": "^1.1.1", + "image-q": "^4.0.0", "omggif": "^1.0.10" } }, @@ -11367,6 +11273,17 @@ "node": ">=10.19.0" } }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -11455,14 +11372,20 @@ "dev": true }, "node_modules/image-q": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz", - "integrity": "sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", "optional": true, - "engines": { - "node": ">=0.9.0" + "dependencies": { + "@types/node": "16.9.1" } }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "optional": true + }, "node_modules/image-size": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz", @@ -15761,25 +15684,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/next/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/next/node_modules/node-releases": { "version": "1.1.77", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", @@ -15843,11 +15747,6 @@ "node": ">=4" } }, - "node_modules/next/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, "node_modules/next/node_modules/watchpack": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", @@ -15860,20 +15759,6 @@ "node": ">=10.13.0" } }, - "node_modules/next/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "node_modules/next/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -15899,6 +15784,44 @@ "node": ">= 10.13" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-html-parser": { "version": "1.4.9", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz", @@ -16669,7 +16592,7 @@ "node_modules/pa11y-ci/node_modules/ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "optional": true, "engines": { "node": ">=0.10.0" @@ -16678,7 +16601,7 @@ "node_modules/pa11y-ci/node_modules/ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", "optional": true, "engines": { "node": ">=0.10.0" @@ -16687,7 +16610,7 @@ "node_modules/pa11y-ci/node_modules/array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", "optional": true, "dependencies": { "array-uniq": "^1.0.1" @@ -16709,7 +16632,7 @@ "node_modules/pa11y-ci/node_modules/chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "optional": true, "dependencies": { "ansi-styles": "^2.2.1", @@ -16768,15 +16691,15 @@ "optional": true }, "node_modules/pa11y-ci/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "optional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -16849,30 +16772,11 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/pa11y-ci/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/pa11y-ci/node_modules/puppeteer": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.19.0.tgz", "integrity": "sha512-2S6E6ygpoqcECaagDbBopoSOPDv0pAZvTbnBgUY+6hq0/XDFDOLEMNlHF/SKJlzcaZ9ckiKjKDuueWI3FN/WXw==", + "deprecated": "Version no longer supported. Upgrade to @latest", "hasInstallScript": true, "optional": true, "dependencies": { @@ -16922,28 +16826,6 @@ "node": ">=0.8.0" } }, - "node_modules/pa11y-ci/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "optional": true - }, - "node_modules/pa11y-ci/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "optional": true - }, - "node_modules/pa11y-ci/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "optional": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/pa11y-ci/node_modules/ws": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", @@ -16957,6 +16839,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/pa11y-reporter-cli/-/pa11y-reporter-cli-1.0.1.tgz", "integrity": "sha512-k+XPl5pBU2R1J6iagGv/GpN/dP7z2cX9WXqO0ALpBwHlHN3ZSukcHCOhuLMmkOZNvufwsvobaF5mnaZxT70YyA==", + "deprecated": "This package is now bundled with pa11y. You can find the latest version of this package in the pa11y repo.", "optional": true, "dependencies": { "chalk": "^2.1.0" @@ -17040,6 +16923,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/pa11y-reporter-csv/-/pa11y-reporter-csv-1.0.0.tgz", "integrity": "sha512-S2gFgbAvONBzAVsVbF8zsYabszrzj7SKhQxrEbw19zF0OFI8wCWn8dFywujYYkg674rmyjweSxSdD+kHTcx4qA==", + "deprecated": "This package is now bundled with pa11y. You can find the latest version of this package in the pa11y repo.", "optional": true, "engines": { "node": ">=8" @@ -17049,6 +16933,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/pa11y-reporter-json/-/pa11y-reporter-json-1.0.0.tgz", "integrity": "sha512-EdLrzh1hyZ8DudCSSrcakgtsHDiSsYNsWLSoEAo1JnFTIK8hYpD7vL+xgd0u+LXDxz9wLLFnckdubpklaRpl/w==", + "deprecated": "This package is now bundled with pa11y. You can find the latest version of this package in the pa11y repo.", "optional": true, "dependencies": { "bfj": "^4.2.3" @@ -17061,6 +16946,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/pa11y-runner-axe/-/pa11y-runner-axe-1.0.2.tgz", "integrity": "sha512-HMw5kQZz16vS5Bhe067esgeuULNzFYP4ixOFAHxOurwGDptlyc2OqH6zfUuK4szB9tbgb5F23v3qz9hCbkGRpw==", + "deprecated": "This package is now bundled with pa11y. You can find the latest version of this package in the pa11y repo.", "optional": true, "dependencies": { "axe-core": "^3.5.1" @@ -17082,6 +16968,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/pa11y-runner-htmlcs/-/pa11y-runner-htmlcs-1.2.1.tgz", "integrity": "sha512-flatSp6moEbqzny18b2IEoDXEWj6xJbJrszdBjUAPQBCN11QRW+SZ0U4uFnxNTLPpXs30N/a9IlH4vYiRr2nPg==", + "deprecated": "This package is now bundled with pa11y. You can find the latest version of this package in the pa11y repo.", "optional": true, "dependencies": { "html_codesniffer": "~2.4.1" @@ -17149,15 +17036,15 @@ "optional": true }, "node_modules/pa11y/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "optional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -17218,6 +17105,7 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.19.0.tgz", "integrity": "sha512-2S6E6ygpoqcECaagDbBopoSOPDv0pAZvTbnBgUY+6hq0/XDFDOLEMNlHF/SKJlzcaZ9ckiKjKDuueWI3FN/WXw==", + "deprecated": "Version no longer supported. Upgrade to @latest", "hasInstallScript": true, "optional": true, "dependencies": { @@ -17527,9 +17415,9 @@ } }, "node_modules/parse-headers": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz", - "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", "optional": true }, "node_modules/parse-json": { @@ -18137,6 +18025,7 @@ "version": "9.1.1", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz", "integrity": "sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==", + "deprecated": "Version no longer supported. Upgrade to @latest", "hasInstallScript": true, "optional": true, "dependencies": { @@ -18157,48 +18046,6 @@ "node": ">=10.18.1" } }, - "node_modules/puppeteer/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/puppeteer/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "optional": true - }, - "node_modules/puppeteer/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "optional": true - }, - "node_modules/puppeteer/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "optional": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/qs": { "version": "6.9.6", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", @@ -18256,11 +18103,11 @@ ] }, "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.0.tgz", + "integrity": "sha512-8HdyR8c0jNVWbYrhUWs9Tg/qAAHgjuJoOIX+mP3eIhgqPO9ytMRURCEFTkOxaHLLsEXo0Cm+bXO5ULuGez+45g==", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -22474,9 +22321,9 @@ } }, "node_modules/website-scraper/node_modules/got": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/got/-/got-12.0.3.tgz", - "integrity": "sha512-hmdcXi/S0gcAtDg4P8j/rM7+j3o1Aq6bXhjxkDhRY2ipe7PHpvx/14DgTY2czHOLaGeU8VRvRecidwfu9qdFug==", + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/got/-/got-12.0.4.tgz", + "integrity": "sha512-2Eyz4iU/ktq7wtMFXxzK7g5p35uNYLLdiZarZ5/Yn3IJlNEpBd5+dCgcAyxN8/8guZLszffwe3wVyw+DEVrpBg==", "optional": true, "dependencies": { "@sindresorhus/is": "^4.6.0", @@ -22501,9 +22348,9 @@ } }, "node_modules/website-scraper/node_modules/http2-wrapper": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.10.tgz", - "integrity": "sha512-QHgsdYkieKp+6JbXP25P+tepqiHYd+FVnDwXpxi/BlUcoIB0nsmTOymTNvETuTO+pDuwcSklPE72VR3DqV+Haw==", + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.11.tgz", + "integrity": "sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==", "optional": true, "dependencies": { "quick-lru": "^5.1.1", @@ -22546,6 +22393,18 @@ "node": ">=12.20" } }, + "node_modules/website-scraper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", @@ -25836,39 +25695,6 @@ "is-plain-object": "^5.0.0", "node-fetch": "^2.6.1", "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } } }, "@octokit/request-error": { @@ -26491,9 +26317,9 @@ "devOptional": true }, "@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "optional": true, "requires": { "@types/node": "*" @@ -26816,7 +26642,7 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "optional": true }, "array.prototype.flat": { @@ -27839,7 +27665,7 @@ "bfj": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/bfj/-/bfj-4.2.4.tgz", - "integrity": "sha1-hfeyNoPCr9wVhgOEotHD+sgO0zo=", + "integrity": "sha512-+c08z3TYqv4dy9b0MAchQsxYlzX9D2asHWW4VhO4ZFTnK7v9ps6iNhEQLqJyEZS6x9G0pgOCk/L7B9E4kp8glQ==", "optional": true, "requires": { "check-types": "^7.3.0", @@ -27877,7 +27703,7 @@ "bmp-js": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM=", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", "optional": true }, "bn.js": { @@ -28131,13 +27957,13 @@ "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "optional": true }, "buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", "optional": true }, "buffer-from": { @@ -28234,6 +28060,12 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, "type-fest": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", @@ -29648,13 +29480,6 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -30767,9 +30592,9 @@ "devOptional": true }, "fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "optional": true, "requires": { "graceful-fs": "^4.2.0", @@ -30791,12 +30616,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "devOptional": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -30819,39 +30638,6 @@ "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", "node-fetch": "^2.6.1" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } } }, "gemoji": { @@ -30918,12 +30704,12 @@ "dev": true }, "gifwrap": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.2.tgz", - "integrity": "sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", + "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", "optional": true, "requires": { - "image-q": "^1.1.1", + "image-q": "^4.0.0", "omggif": "^1.0.10" } }, @@ -31644,6 +31430,13 @@ "requires": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" + }, + "dependencies": { + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + } } }, "https-browserify": { @@ -31699,10 +31492,21 @@ "dev": true }, "image-q": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz", - "integrity": "sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY=", - "optional": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "optional": true, + "requires": { + "@types/node": "16.9.1" + }, + "dependencies": { + "@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "optional": true + } + } }, "image-size": { "version": "1.0.0", @@ -34824,14 +34628,6 @@ } } }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, "node-releases": { "version": "1.1.77", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz", @@ -34878,11 +34674,6 @@ } } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, "watchpack": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", @@ -34891,20 +34682,6 @@ "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } } } }, @@ -34930,6 +34707,35 @@ "propagate": "^2.0.0" } }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-html-parser": { "version": "1.4.9", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz", @@ -35554,15 +35360,15 @@ } }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "optional": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -35679,19 +35485,19 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "optional": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", "optional": true }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", "optional": true, "requires": { "array-uniq": "^1.0.1" @@ -35710,7 +35516,7 @@ "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "optional": true, "requires": { "ansi-styles": "^2.2.1", @@ -35762,15 +35568,15 @@ } }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "optional": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } @@ -35827,15 +35633,6 @@ "minimist": "^1.2.6" } }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "optional": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, "puppeteer": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.19.0.tgz", @@ -35876,28 +35673,6 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "optional": true }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "optional": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "optional": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "optional": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "ws": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", @@ -36240,9 +36015,9 @@ } }, "parse-headers": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz", - "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", "optional": true }, "parse-json": { @@ -36728,39 +36503,6 @@ "tar-fs": "^2.0.0", "unbzip2-stream": "^1.3.3", "ws": "^7.2.3" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "optional": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "optional": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "optional": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "optional": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } } }, "qs": { @@ -36793,9 +36535,9 @@ "dev": true }, "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.0.tgz", + "integrity": "sha512-8HdyR8c0jNVWbYrhUWs9Tg/qAAHgjuJoOIX+mP3eIhgqPO9ytMRURCEFTkOxaHLLsEXo0Cm+bXO5ULuGez+45g==" }, "random-bytes": { "version": "1.0.0", @@ -40026,9 +39768,9 @@ "optional": true }, "got": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/got/-/got-12.0.3.tgz", - "integrity": "sha512-hmdcXi/S0gcAtDg4P8j/rM7+j3o1Aq6bXhjxkDhRY2ipe7PHpvx/14DgTY2czHOLaGeU8VRvRecidwfu9qdFug==", + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/got/-/got-12.0.4.tgz", + "integrity": "sha512-2Eyz4iU/ktq7wtMFXxzK7g5p35uNYLLdiZarZ5/Yn3IJlNEpBd5+dCgcAyxN8/8guZLszffwe3wVyw+DEVrpBg==", "optional": true, "requires": { "@sindresorhus/is": "^4.6.0", @@ -40047,9 +39789,9 @@ } }, "http2-wrapper": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.10.tgz", - "integrity": "sha512-QHgsdYkieKp+6JbXP25P+tepqiHYd+FVnDwXpxi/BlUcoIB0nsmTOymTNvETuTO+pDuwcSklPE72VR3DqV+Haw==", + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.11.tgz", + "integrity": "sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==", "optional": true, "requires": { "quick-lru": "^5.1.1", @@ -40073,6 +39815,12 @@ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "optional": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "optional": true } } }, diff --git a/package.json b/package.json index 354a8996f88e..2920614ef9ac 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "overload-protection": "^1.2.3", "parse5": "^6.0.1", "port-used": "^2.0.8", + "quick-lru": "6.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-markdown": "^8.0.0", diff --git a/pages/_app.tsx b/pages/_app.tsx index eab4108e8211..27494db15fcd 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -10,14 +10,27 @@ import '../stylesheets/index.scss' import events from 'components/lib/events' import experiment from 'components/lib/experiment' import { LanguagesContext, LanguagesContextT } from 'components/context/LanguagesContext' +import { + DotComAuthenticatedContext, + DotComAuthenticatedContextT, +} from 'components/context/DotComAuthenticatedContext' import { defaultComponentTheme } from 'lib/get-theme.js' type MyAppProps = AppProps & { csrfToken: string + isDotComAuthenticated: boolean themeProps: typeof defaultComponentTheme & Pick languagesContext: LanguagesContextT + dotComAuthenticatedContext: DotComAuthenticatedContextT } -const MyApp = ({ Component, pageProps, csrfToken, themeProps, languagesContext }: MyAppProps) => { +const MyApp = ({ + Component, + pageProps, + csrfToken, + themeProps, + languagesContext, + dotComAuthenticatedContext, +}: MyAppProps) => { useEffect(() => { events() experiment() @@ -58,7 +71,9 @@ const MyApp = ({ Component, pageProps, csrfToken, themeProps, languagesContext } preventSSRMismatch > - + + + @@ -66,6 +81,10 @@ const MyApp = ({ Component, pageProps, csrfToken, themeProps, languagesContext } ) } +// Remember, function is only called once if the rendered page can +// be in-memory cached. But still, the `` component will be +// executed every time **in the client** if it was the first time +// ever (since restart) or from a cached HTML. MyApp.getInitialProps = async (appContext: AppContext) => { const { ctx } = appContext // calls page's `getInitialProps` and fills `appProps.pageProps` @@ -78,7 +97,8 @@ MyApp.getInitialProps = async (appContext: AppContext) => { ...appProps, themeProps: getTheme(req), csrfToken: req?.csrfToken?.() || '', - languagesContext: { languages: req.context.languages }, + languagesContext: { languages: req.context.languages, userLanguage: req.context.userLanguage }, + dotComAuthenticatedContext: { isDotComAuthenticated: Boolean(req.cookies?.dotcom_user) }, } } diff --git a/script/search/sync.js b/script/search/sync.js index 81d995118eda..cde5e3014713 100644 --- a/script/search/sync.js +++ b/script/search/sync.js @@ -11,6 +11,7 @@ import LunrIndex from './lunr-search-index.js' // Build a search data file for every combination of product version and language // e.g. `github-docs-dotcom-en.json` and `github-docs-2.14-ja.json` export default async function syncSearchIndexes(opts = {}) { + const t0 = new Date() if (opts.language) { if (!Object.keys(languages).includes(opts.language)) { console.log( @@ -89,6 +90,9 @@ export default async function syncSearchIndexes(opts = {}) { } } } + const t1 = new Date() + const tookSec = (t1.getTime() - t0.getTime()) / 1000 console.log('\nDone!') + console.log(`Took ${tookSec.toFixed(1)} seconds`) } diff --git a/tests/helpers/script-data.js b/tests/helpers/script-data.js new file mode 100644 index 000000000000..fd4887edb878 --- /dev/null +++ b/tests/helpers/script-data.js @@ -0,0 +1,29 @@ +const NEXT_DATA_QUERY = 'script#__NEXT_DATA__' +const PRIMER_DATA_QUERY = 'script#__PRIMER_DATA__' + +function getScriptData($, key) { + const data = $(key) + if (!data.length === 1) { + throw new Error(`Not exactly 1 element match for '${key}'. Found ${data.length}`) + } + return JSON.parse(data.get()[0].children[0].data) +} + +export const getNextData = ($) => getScriptData($, NEXT_DATA_QUERY) +export const getPrimerData = ($) => getScriptData($, PRIMER_DATA_QUERY) + +export const getUserLanguage = ($) => { + // Because the page might come from the middleware rendering cache, + // the DOM won't get updated until the first client-side React render. + // But we can assert the data that would be used for that first render. + const { props } = getNextData($) + return props.languagesContext.userLanguage +} + +export const getIsDotComAuthenticated = ($) => { + // Because the page might come from the middleware rendering cache, + // the DOM won't get updated until the first client-side React render. + // But we can assert the data that would be used for that first render. + const { props } = getNextData($) + return props.dotComAuthenticatedContext.isDotComAuthenticated +} diff --git a/tests/rendering/head.js b/tests/rendering/head.js index 1a117d8c6012..111ca523caaa 100644 --- a/tests/rendering/head.js +++ b/tests/rendering/head.js @@ -13,7 +13,16 @@ describe('', () => { expect($hreflangs.length).toEqual(Object.keys(languages).length) expect($('link[href="https://docs.github.com/cn"]').length).toBe(1) expect($('link[href="https://docs.github.com/ja"]').length).toBe(1) - expect($('link[hrefLang="en"]').length).toBe(1) + // Due to a bug in either NextJS, JSX, or TypeScript, + // when put `` in a .tsx file, this incorrectly + // gets rendered out as `` in the final HTML. + // Note the uppercase L. It's supposed to become ``. + // When cheerio serializes to HTML, it gets this right so it lowercases + // the attribute. So if this rendering in this jest test was the first + // ever cold hit, you might get the buggy HTML from React or you + // might get the correct HTML from cheerio's `.html()` serializer. + // This is why we're looking for either. + expect($('link[hreflang="en"]').length + $('link[hrefLang="en"]').length).toBe(1) }) test('includes page intro in `description` meta tag', async () => { diff --git a/tests/rendering/header.js b/tests/rendering/header.js index 78c223a31d6e..221c15ac219a 100644 --- a/tests/rendering/header.js +++ b/tests/rendering/header.js @@ -1,7 +1,8 @@ -import { jest } from '@jest/globals' +import { expect, jest } from '@jest/globals' import { getDOM } from '../helpers/e2etest.js' import { oldestSupported } from '../../lib/enterprise-server-releases.js' +import { getUserLanguage } from '../helpers/script-data.js' describe('header', () => { jest.setTimeout(5 * 60 * 1000) @@ -91,54 +92,31 @@ describe('header', () => { test("renders a link to the same page in user's preferred language, if available", async () => { const headers = { 'accept-language': 'ja' } const $ = await getDOM('/en', { headers }) - expect($('[data-testid=header-notification][data-type=TRANSLATION]').length).toBe(1) - expect($('[data-testid=header-notification] a[href*="/ja"]').length).toBe(1) + expect(getUserLanguage($)).toBe('ja') }) test("renders a link to the same page if user's preferred language is Chinese - PRC", async () => { const headers = { 'accept-language': 'zh-CN' } const $ = await getDOM('/en', { headers }) - expect($('[data-testid=header-notification][data-type=TRANSLATION]').length).toBe(1) - expect($('[data-testid=header-notification] a[href*="/cn"]').length).toBe(1) - }) - - test("does not render a link when user's preferred language is Chinese - Taiwan", async () => { - const headers = { 'accept-language': 'zh-TW' } - const $ = await getDOM('/en', { headers }) - expect($('[data-testid=header-notification]').length).toBe(0) - }) - - test("does not render a link when user's preferred language is English", async () => { - const headers = { 'accept-language': 'en' } - const $ = await getDOM('/en', { headers }) - expect($('[data-testid=header-notification]').length).toBe(0) + expect(getUserLanguage($)).toBe('cn') }) test("renders a link to the same page in user's preferred language from multiple, if available", async () => { const headers = { 'accept-language': 'ja, *;q=0.9' } const $ = await getDOM('/en', { headers }) - expect($('[data-testid=header-notification][data-type=TRANSLATION]').length).toBe(1) - expect($('[data-testid=header-notification] a[href*="/ja"]').length).toBe(1) + expect(getUserLanguage($)).toBe('ja') }) test("renders a link to the same page in user's preferred language with weights, if available", async () => { const headers = { 'accept-language': 'ja;q=1.0, *;q=0.9' } const $ = await getDOM('/en', { headers }) - expect($('[data-testid=header-notification][data-type=TRANSLATION]').length).toBe(1) - expect($('[data-testid=header-notification] a[href*="/ja"]').length).toBe(1) + expect(getUserLanguage($)).toBe('ja') }) test("renders a link to the user's 2nd preferred language if 1st is not available", async () => { const headers = { 'accept-language': 'zh-TW,zh;q=0.9,ja *;q=0.8' } const $ = await getDOM('/en', { headers }) - expect($('[data-testid=header-notification][data-type=TRANSLATION]').length).toBe(1) - expect($('[data-testid=header-notification] a[href*="/ja"]').length).toBe(1) - }) - - test('renders no notices if no language preference is available', async () => { - const headers = { 'accept-language': 'zh-TW,zh;q=0.9,zh-SG *;q=0.8' } - const $ = await getDOM('/en', { headers }) - expect($('[data-testid=header-notification]').length).toBe(0) + expect(getUserLanguage($)).toBe('ja') }) }) diff --git a/tests/rendering/render-caching.js b/tests/rendering/render-caching.js new file mode 100644 index 000000000000..49c0a2442d69 --- /dev/null +++ b/tests/rendering/render-caching.js @@ -0,0 +1,160 @@ +import cheerio from 'cheerio' +import { expect, jest, test } from '@jest/globals' + +import { get } from '../helpers/e2etest.js' +import { PREFERRED_LOCALE_COOKIE_NAME } from '../../middleware/detect-language.js' +import { + getNextData, + getPrimerData, + getUserLanguage, + getIsDotComAuthenticated, +} from '../helpers/script-data.js' + +const serializeTheme = (theme) => { + return encodeURIComponent(JSON.stringify(theme)) +} + +describe('in-memory render caching', () => { + jest.setTimeout(30 * 1000) + + test('second render should be a cache hit with different csrf-token', async () => { + const res = await get('/en') + // Because these are effectively end-to-end tests, you can't expect + // the first request to be a cache miss because another end-to-end + // test might have "warmed up" this endpoint. + expect(res.headers['x-middleware-cache']).toBeTruthy() + const $1 = cheerio.load(res.text) + const res2 = await get('/en') + expect(res2.headers['x-middleware-cache']).toBe('hit') + const $2 = cheerio.load(res2.text) + const csrfTokenHTML1 = $1('meta[name="csrf-token"]').attr('content') + const csrfTokenHTML2 = $2('meta[name="csrf-token"]').attr('content') + expect(csrfTokenHTML1).not.toBe(csrfTokenHTML2) + // The HTML is one thing, we also need to check that the + // __NEXT_DATA__ serialized (JSON) state is different. + const csrfTokenNEXT1 = getNextData($1).props.csrfToken + const csrfTokenNEXT2 = getNextData($2).props.csrfToken + expect(csrfTokenHTML1).toBe(csrfTokenNEXT1) + expect(csrfTokenHTML2).toBe(csrfTokenNEXT2) + expect(csrfTokenNEXT1).not.toBe(csrfTokenNEXT2) + }) + + test('second render should be a cache hit with different dotcom-auth', async () => { + // Anonymous first + const res = await get('/en') + // Because these are effectively end-to-end tests, you can't expect + // the first request to be a cache miss because another end-to-end + // test might have "warmed up" this endpoint. + expect(res.headers['x-middleware-cache']).toBeTruthy() + const $1 = cheerio.load(res.text) + const res2 = await get('/en', { + headers: { + cookie: 'dotcom_user=peterbe', + }, + }) + expect(res2.headers['x-middleware-cache']).toBe('hit') + const $2 = cheerio.load(res2.text) + // The HTML is one thing, we also need to check that the + // __NEXT_DATA__ serialized (JSON) state is different. + const dotcomAuthNEXT1 = getIsDotComAuthenticated($1) + const dotcomAuthNEXT2 = getIsDotComAuthenticated($2) + expect(dotcomAuthNEXT1).not.toBe(dotcomAuthNEXT2) + }) + + test('second render should be a cache hit with different theme properties', async () => { + const cookieValue1 = { + color_mode: 'light', + light_theme: { name: 'light', color_mode: 'light' }, + dark_theme: { name: 'dark_high_contrast', color_mode: 'dark' }, + } + // Light mode first + const res1 = await get('/en', { + headers: { + cookie: `color_mode=${serializeTheme(cookieValue1)}`, + }, + }) + // Because these are effectively end-to-end tests, you can't expect + // the first request to be a cache miss because another end-to-end + // test might have "warmed up" this endpoint. + expect(res1.headers['x-middleware-cache']).toBeTruthy() + const $1 = cheerio.load(res1.text) + expect($1('body').data('color-mode')).toBe(cookieValue1.color_mode) + const themeProps1 = getNextData($1).props.themeProps + expect(themeProps1.colorMode).toBe('day') + + const cookieValue2 = { + color_mode: 'dark', + light_theme: { name: 'light', color_mode: 'light' }, + dark_theme: { name: 'dark_high_contrast', color_mode: 'dark' }, + } + const res2 = await get('/en', { + headers: { + cookie: `color_mode=${serializeTheme(cookieValue2)}`, + }, + }) + expect(res2.headers['x-middleware-cache']).toBeTruthy() + const $2 = cheerio.load(res2.text) + expect($2('body').data('color-mode')).toBe(cookieValue2.color_mode) + const themeProps2 = getNextData($2).props.themeProps + expect(themeProps2.colorMode).toBe('night') + }) + + test('second render should be cache hit with different resolvedServerColorMode in __PRIMER_DATA__', async () => { + await get('/en') // first render to assert the next render comes from cache + + const res = await get('/en', { + headers: { + cookie: `color_mode=${serializeTheme({ + color_mode: 'dark', + light_theme: { name: 'light', color_mode: 'light' }, + dark_theme: { name: 'dark_high_contrast', color_mode: 'dark' }, + })}`, + }, + }) + expect(res.headers['x-middleware-cache']).toBeTruthy() + const $ = cheerio.load(res.text) + const data = getPrimerData($) + expect(data.resolvedServerColorMode).toBe('night') + + // Now do it all over again but with a light color mode + const res2 = await get('/en', { + headers: { + cookie: `color_mode=${serializeTheme({ + color_mode: 'light', + light_theme: { name: 'light', color_mode: 'light' }, + dark_theme: { name: 'dark_high_contrast', color_mode: 'dark' }, + })}`, + }, + }) + expect(res2.headers['x-middleware-cache']).toBeTruthy() + const $2 = cheerio.load(res2.text) + const data2 = getPrimerData($2) + expect(data2.resolvedServerColorMode).toBe('day') + }) + + test('user-language, by header, in meta tag', async () => { + await get('/en') // first render to assert the next render comes from cache + + const res = await get('/en', { + headers: { 'accept-language': 'ja;q=1.0, *;q=0.9' }, + }) + expect(res.headers['x-middleware-cache']).toBeTruthy() + const $ = cheerio.load(res.text) + const userLanguage = getUserLanguage($) + expect(userLanguage).toBe('ja') + }) + + test('user-language, by cookie, in meta tag', async () => { + await get('/en') // first render to assert the next render comes from cache + + const res = await get('/en', { + headers: { + Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=ja`, + }, + }) + expect(res.headers['x-middleware-cache']).toBeTruthy() + const $ = cheerio.load(res.text) + const userLanguage = getUserLanguage($) + expect(userLanguage).toBe('ja') + }) +}) diff --git a/tests/rendering/signup-button.js b/tests/rendering/signup-button.js index 64881cdab841..673f9521b527 100644 --- a/tests/rendering/signup-button.js +++ b/tests/rendering/signup-button.js @@ -1,23 +1,14 @@ import { jest, describe, expect } from '@jest/globals' import { getDOM } from '../helpers/e2etest.js' +import { getIsDotComAuthenticated } from '../helpers/script-data.js' describe('GHEC sign up button', () => { jest.setTimeout(60 * 1000) - test('present by default', async () => { + test('false by default', async () => { const $ = await getDOM('/en') - expect($('a[href^="https://github.com/signup"]').length).toBeGreaterThan(0) - }) - - test('present on enterprise-cloud pages', async () => { - const $ = await getDOM('/en/enterprise-cloud@latest') - expect($('a[href^="https://github.com/signup"]').length).toBeGreaterThan(0) - }) - - test('not present on enterprise-server pages', async () => { - const $ = await getDOM('/en/enterprise-server@latest') - expect($('a[href^="https://github.com/signup"]').length).toBe(0) + expect(getIsDotComAuthenticated($)).toBe(false) }) test('not present if dotcom_user cookie', async () => { @@ -26,6 +17,15 @@ describe('GHEC sign up button', () => { cookie: 'dotcom_user=peterbe', }, }) - expect($('a[href^="https://github.com/signup"]').length).toBe(0) + expect(getIsDotComAuthenticated($)).toBe(true) + + // Do another request, same URL, but different cookie, just to + // make sure the server-side rendering cache isn't failing. + const $2 = await getDOM('/en', { + headers: { + cookie: 'bla=bla', + }, + }) + expect(getIsDotComAuthenticated($2)).toBe(false) }) })