cache external link checking to disk file (#32082)

This commit is contained in:
Peter Bengtsson 2022-11-10 14:38:08 +01:00 коммит произвёл GitHub
Родитель 4aff99e80f
Коммит 46fbab792e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 136 добавлений и 307 удалений

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

@ -6,6 +6,8 @@ import cheerio from 'cheerio'
import coreLib from '@actions/core'
import got, { RequestError } from 'got'
import chalk from 'chalk'
import { Low } from 'lowdb'
import { JSONFile } from 'lowdb/node'
import shortVersions from '../../middleware/contextualizers/short-versions.js'
import contextualize from '../../middleware/context.js'
@ -31,6 +33,29 @@ Object.entries(STATIC_PREFIXES).forEach(([key, value]) => {
}
})
// By default, we don't cache external link checks to disk.
// By setting this env var to something >0, it enables the disk-based
// caching of external links.
const EXTERNAL_LINK_CHECKER_MAX_AGE_MS =
parseInt(process.env.EXTERNAL_LINK_CHECKER_MAX_AGE_DAYS || 0) * 24 * 60 * 60 * 1000
const EXTERNAL_LINK_CHECKER_DB =
process.env.EXTERNAL_LINK_CHECKER_DB || 'external-link-checker-db.json'
const adapter = new JSONFile(EXTERNAL_LINK_CHECKER_DB)
const externalLinkCheckerDB = new Low(adapter)
// Given a number and a percentage, return the same number with a *percentage*
// max change of making a bit larger or smaller.
// E.g. `jitter(55, 10)` will return a value between `[55 - 55/10: 55 + 55/10]`
// This is useful to avoid the caching timestamps all getting the same
// numbers from the day it started which means that they don't ALL expire
// on the same day but start to expire in a bit of a "random pattern" so
// you don't get all or nothing.
function jitter(base, percentage) {
const r = percentage / 100
const negative = Math.random() > 0.5 ? -1 : 1
return base + base * Math.random() * r * negative
}
// Return a function that can as quickly as possible check if a certain
// href input should be skipped.
// Do this so we can use a `Set` and a `iterable.some()` for a speedier
@ -186,12 +211,17 @@ async function main(core, octokit, uploadArtifact, opts = {}) {
)
}
await externalLinkCheckerDB.read()
externalLinkCheckerDB.data ||= { urls: {} }
debugTimeStart(core, 'processPages')
const flawsGroups = await Promise.all(
pages.map((page) => processPage(core, page, pageMap, redirects, opts))
pages.map((page) => processPage(core, page, pageMap, redirects, opts, externalLinkCheckerDB))
)
debugTimeEnd(core, 'processPages')
await externalLinkCheckerDB.write()
const flaws = flawsGroups.flat()
printGlobalCacheHitRatio(core)
@ -518,12 +548,12 @@ function getPages(pageList, languages, filters, files, max) {
.slice(0, max ? Math.min(max, pageList.length) : pageList.length)
}
async function processPage(core, page, pageMap, redirects, opts) {
async function processPage(core, page, pageMap, redirects, opts, db) {
const { verbose, verboseUrl, bail } = opts
const allFlawsEach = await Promise.all(
page.permalinks.map((permalink) => {
return processPermalink(core, permalink, page, pageMap, redirects, opts)
return processPermalink(core, permalink, page, pageMap, redirects, opts, db)
})
)
@ -545,7 +575,7 @@ async function processPage(core, page, pageMap, redirects, opts) {
return allFlaws
}
async function processPermalink(core, permalink, page, pageMap, redirects, opts) {
async function processPermalink(core, permalink, page, pageMap, redirects, opts, db) {
const {
level = 'critical',
checkAnchors,
@ -583,7 +613,8 @@ async function processPermalink(core, permalink, page, pageMap, redirects, opts)
pageMap,
checkAnchors,
checkExternalLinks,
{ verbose, patient }
{ verbose, patient },
db
)
if (flaw) {
@ -727,7 +758,8 @@ async function checkHrefLink(
pageMap,
checkAnchors = false,
checkExternalLinks = false,
{ verbose = false, patient = false } = {}
{ verbose = false, patient = false } = {},
db = null
) {
if (href === '#') {
if (checkAnchors) {
@ -781,13 +813,53 @@ async function checkHrefLink(
if (linksToSkip(href)) {
return
}
const { ok, ...info } = await checkExternalURL(core, href, { verbose, patient })
const { ok, ...info } = await checkExternalURLCached(core, href, { verbose, patient }, db)
if (!ok) {
return { CRITICAL: `Broken external link (${JSON.stringify(info)})`, isExternal: true }
}
}
}
// Can't do this memoization within the checkExternalURL because it can
// return a Promise since it already collates multiple URLs under the
// same cache key.
async function checkExternalURLCached(core, href, { verbose, patient }, db) {
const cacheMaxAge = EXTERNAL_LINK_CHECKER_MAX_AGE_MS
const timestamp = new Date().getTime()
const url = href.split('#')[0]
if (cacheMaxAge) {
const tooOld = timestamp - Math.floor(jitter(cacheMaxAge, 10))
if (db && db.data.urls[url]) {
if (db.data.urls[url].timestamp > tooOld) {
if (verbose) {
core.debug(`External URL ${url} in cache`)
}
return db.data.urls[url].result
} else if (verbose) {
core.info(`External URL ${url} in cache but too old`)
// Delete it so the cache file don't bloat infinitely
delete db.data.urls[url]
}
}
}
const result = await checkExternalURL(core, href, { verbose, patient })
if (cacheMaxAge) {
// By only cache storing successful results, we give the system a chance
// to try 40xx and 50x errors another go.
if (db && result.ok) {
db.data.urls[url] = {
timestamp,
result,
}
}
}
return result
}
const _fetchCache = new Map()
async function checkExternalURL(core, url, { verbose = false, patient = false } = {}) {
if (!url.startsWith('https://')) throw new Error('Invalid URL')

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

@ -54,6 +54,12 @@ jobs:
if: ${{ github.repository == 'github/docs-internal' }}
run: .github/actions-scripts/merge-early-access.sh
- name: Restore disk-cache file for external link checking
uses: actions/cache@48af2dc4a9e8278b89d7fa154b955c30c6aaab09
with:
path: external-link-checker-db.json
key: external-link-checker-${{ hashFiles('.github/actions/rendered-content-link-checker.js') }}
- name: Run link checker
env:
LEVEL: 'critical'
@ -67,6 +73,10 @@ jobs:
CREATE_REPORT: true
CHECK_EXTERNAL_LINKS: true
PATIENT: true
# This means that we'll *re-check* external URLs once a week.
# But mind you that the number has a 10% chance of "jitter"
# to avoid a stampeding herd when they all expire some day.
EXTERNAL_LINK_CHECKER_MAX_AGE_DAYS: 7
timeout-minutes: 30
run: node .github/actions/rendered-content-link-checker.js

1
.gitignore поставляемый
Просмотреть файл

@ -29,3 +29,4 @@ user-code/
# Logs from scripts
script/logs/
external-link-checker-db.json

345
package-lock.json сгенерированный
Просмотреть файл

@ -50,6 +50,7 @@
"liquidjs": "9.22.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lowdb": "5.0.5",
"lunr": "^2.3.9",
"lunr-languages": "^1.9.0",
"mdast-util-from-markdown": "^1.2.0",
@ -3480,201 +3481,6 @@
"resolved": "https://registry.npmjs.org/@next/env/-/env-12.2.4.tgz",
"integrity": "sha512-/gApFXWk5CCLFQJL5IYJXxPQuG5tz5nPX4l27A9Zm/+wJxiwFrRSP54AopDxIv4JRp/rGwcgk/lZS/0Clw8jYA=="
},
"node_modules/@next/swc-android-arm-eabi": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.2.4.tgz",
"integrity": "sha512-P4YSFNpmXXSnn3P1qsOAqz+MX3On9fHrlc8ovb/CFJJoU+YLCR53iCEwfw39e0IZEgDA7ttgr108plF8mxaX0g==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-android-arm64": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.2.4.tgz",
"integrity": "sha512-4o2n14E18O+8xHlf6dgJsWPXN9gmSmfIe2Z0EqKDIPBBkFt/2CyrH0+vwHnL2l7xkDHhOGfZYcYIWVUR5aNu0A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.2.4.tgz",
"integrity": "sha512-DcUO6MGBL9E3jj5o86MUnTOy4WawIJJhyCcFYO4f51sbl7+uPIYIx40eo98A6NwJEXazCqq1hLeqOaNTAIvDiQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.2.4.tgz",
"integrity": "sha512-IUlFMqeLjdIzDorrGC2Dt+2Ae3DbKQbRzCzmDq4/CP1+jJGeDXo/2AHnlE+WYnwQAC4KtAz6pbVnd3KstZWsVA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-freebsd-x64": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.2.4.tgz",
"integrity": "sha512-475vwyWcjnyDVDWLgAATP0HI8W1rwByc+uXk1B6KkAVFhkoDgH387LW0uNqxavK+VxCzj3avQXX/58XDvxtSlg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm-gnueabihf": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.2.4.tgz",
"integrity": "sha512-qZW+L3iG3XSGtlOPmD5RRWXyk6ZNdscLV0BQjuDvP+exTg+uixqHXOHz0/GVATIJEBQOF0Kew7jAXVXEP+iRTQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.2.4.tgz",
"integrity": "sha512-fEPRjItWYaKyyG9N+2HIA59OBHIhk7WC+Rh+LwXsh0pQe870Ykpek3KQs0umjsrEGe57NyMomq3f80/N8taDvA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.2.4.tgz",
"integrity": "sha512-rnCTzXII0EBCcFn9P5s/Dho2kPUMSX/bP0iOAj8wEI/IxUEfEElbin89zJoNW30cycHu19xY8YP4K2+hzciPzQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.2.4.tgz",
"integrity": "sha512-PhXX6NSuIuhHInxPY2VkG2Bl7VllsD3Cjx+pQcS1wTym7Zt7UoLvn05PkRrkiyIkvR+UXnqPUM3TYiSbnemXEw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.2.4.tgz",
"integrity": "sha512-GmC/QROiUZpFirHRfPQqMyCXZ+5+ndbBZrMvL74HtQB/CKXB8K1VM+rvy9Gp/5OaU8Rxp48IcX79NOfI2LiXlA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.2.4.tgz",
"integrity": "sha512-9XKoCXbNZuaMRPtcKQz3+hgVpkMosaLlcxHFXT8/j4w61k7/qvEbrkMDS9WHNrD/xVcLycwhPRgXcns2K1BdBQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.2.4.tgz",
"integrity": "sha512-hEyRieZKH9iw4AzvXaQ+Fyb98k0G/o9QcRGxA1/O/O/elf1+Qvuwb15phT8GbVtIeNziy66XTPOhKKfdr8KyUg==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.2.4.tgz",
"integrity": "sha512-5Pl1tdMJWLy4rvzU1ecx0nHWgDPqoYuvYoXE/5X0Clu9si/yOuBIj573F2kOTY7mu0LX2wgCJVSnyK0abHBxIw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -5650,9 +5456,9 @@
}
},
"node_modules/babel-loader": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.0.1.tgz",
"integrity": "sha512-szYjslOXFlj/po5KfrVmiuBAcI6GVHFuAgC96Qd6mMPHdwl4lmAJkYtvjQ1RxxPjgdkKjd3LQgXDE4jxEutNuw==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz",
"integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==",
"dev": true,
"dependencies": {
"find-cache-dir": "^3.3.2",
@ -9564,20 +9370,6 @@
"devOptional": true,
"license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"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",
"license": "MIT"
@ -14245,6 +14037,20 @@
"loose-envify": "cli.js"
}
},
"node_modules/lowdb": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.0.5.tgz",
"integrity": "sha512-7EWKmIMhNKA8TXFhL8t0p6N2LC53l3ZqsWQGSksGhhjrcms9rbKlyrAh2PzSGK5v0KPJ2W5VItBnC3NDRzOnzQ==",
"dependencies": {
"steno": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/lower-case": {
"version": "2.0.2",
"dev": true,
@ -18737,6 +18543,17 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/steno": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz",
"integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/stream-combiner": {
"version": "0.0.4",
"dev": true,
@ -23139,84 +22956,6 @@
"resolved": "https://registry.npmjs.org/@next/env/-/env-12.2.4.tgz",
"integrity": "sha512-/gApFXWk5CCLFQJL5IYJXxPQuG5tz5nPX4l27A9Zm/+wJxiwFrRSP54AopDxIv4JRp/rGwcgk/lZS/0Clw8jYA=="
},
"@next/swc-android-arm-eabi": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.2.4.tgz",
"integrity": "sha512-P4YSFNpmXXSnn3P1qsOAqz+MX3On9fHrlc8ovb/CFJJoU+YLCR53iCEwfw39e0IZEgDA7ttgr108plF8mxaX0g==",
"optional": true
},
"@next/swc-android-arm64": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.2.4.tgz",
"integrity": "sha512-4o2n14E18O+8xHlf6dgJsWPXN9gmSmfIe2Z0EqKDIPBBkFt/2CyrH0+vwHnL2l7xkDHhOGfZYcYIWVUR5aNu0A==",
"optional": true
},
"@next/swc-darwin-arm64": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.2.4.tgz",
"integrity": "sha512-DcUO6MGBL9E3jj5o86MUnTOy4WawIJJhyCcFYO4f51sbl7+uPIYIx40eo98A6NwJEXazCqq1hLeqOaNTAIvDiQ==",
"optional": true
},
"@next/swc-darwin-x64": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.2.4.tgz",
"integrity": "sha512-IUlFMqeLjdIzDorrGC2Dt+2Ae3DbKQbRzCzmDq4/CP1+jJGeDXo/2AHnlE+WYnwQAC4KtAz6pbVnd3KstZWsVA==",
"optional": true
},
"@next/swc-freebsd-x64": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.2.4.tgz",
"integrity": "sha512-475vwyWcjnyDVDWLgAATP0HI8W1rwByc+uXk1B6KkAVFhkoDgH387LW0uNqxavK+VxCzj3avQXX/58XDvxtSlg==",
"optional": true
},
"@next/swc-linux-arm-gnueabihf": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.2.4.tgz",
"integrity": "sha512-qZW+L3iG3XSGtlOPmD5RRWXyk6ZNdscLV0BQjuDvP+exTg+uixqHXOHz0/GVATIJEBQOF0Kew7jAXVXEP+iRTQ==",
"optional": true
},
"@next/swc-linux-arm64-gnu": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.2.4.tgz",
"integrity": "sha512-fEPRjItWYaKyyG9N+2HIA59OBHIhk7WC+Rh+LwXsh0pQe870Ykpek3KQs0umjsrEGe57NyMomq3f80/N8taDvA==",
"optional": true
},
"@next/swc-linux-arm64-musl": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.2.4.tgz",
"integrity": "sha512-rnCTzXII0EBCcFn9P5s/Dho2kPUMSX/bP0iOAj8wEI/IxUEfEElbin89zJoNW30cycHu19xY8YP4K2+hzciPzQ==",
"optional": true
},
"@next/swc-linux-x64-gnu": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.2.4.tgz",
"integrity": "sha512-PhXX6NSuIuhHInxPY2VkG2Bl7VllsD3Cjx+pQcS1wTym7Zt7UoLvn05PkRrkiyIkvR+UXnqPUM3TYiSbnemXEw==",
"optional": true
},
"@next/swc-linux-x64-musl": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.2.4.tgz",
"integrity": "sha512-GmC/QROiUZpFirHRfPQqMyCXZ+5+ndbBZrMvL74HtQB/CKXB8K1VM+rvy9Gp/5OaU8Rxp48IcX79NOfI2LiXlA==",
"optional": true
},
"@next/swc-win32-arm64-msvc": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.2.4.tgz",
"integrity": "sha512-9XKoCXbNZuaMRPtcKQz3+hgVpkMosaLlcxHFXT8/j4w61k7/qvEbrkMDS9WHNrD/xVcLycwhPRgXcns2K1BdBQ==",
"optional": true
},
"@next/swc-win32-ia32-msvc": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.2.4.tgz",
"integrity": "sha512-hEyRieZKH9iw4AzvXaQ+Fyb98k0G/o9QcRGxA1/O/O/elf1+Qvuwb15phT8GbVtIeNziy66XTPOhKKfdr8KyUg==",
"optional": true
},
"@next/swc-win32-x64-msvc": {
"version": "12.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.2.4.tgz",
"integrity": "sha512-5Pl1tdMJWLy4rvzU1ecx0nHWgDPqoYuvYoXE/5X0Clu9si/yOuBIj573F2kOTY7mu0LX2wgCJVSnyK0abHBxIw==",
"optional": true
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -24768,9 +24507,9 @@
}
},
"babel-loader": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.0.1.tgz",
"integrity": "sha512-szYjslOXFlj/po5KfrVmiuBAcI6GVHFuAgC96Qd6mMPHdwl4lmAJkYtvjQ1RxxPjgdkKjd3LQgXDE4jxEutNuw==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz",
"integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==",
"dev": true,
"requires": {
"find-cache-dir": "^3.3.2",
@ -27509,13 +27248,6 @@
"version": "1.0.0",
"devOptional": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1"
},
@ -30861,6 +30593,14 @@
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"lowdb": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.0.5.tgz",
"integrity": "sha512-7EWKmIMhNKA8TXFhL8t0p6N2LC53l3ZqsWQGSksGhhjrcms9rbKlyrAh2PzSGK5v0KPJ2W5VItBnC3NDRzOnzQ==",
"requires": {
"steno": "^3.0.0"
}
},
"lower-case": {
"version": "2.0.2",
"dev": true,
@ -33757,6 +33497,11 @@
"state-toggle": {
"version": "1.0.3"
},
"steno": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz",
"integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw=="
},
"stream-combiner": {
"version": "0.0.4",
"dev": true,

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

@ -52,6 +52,7 @@
"liquidjs": "9.22.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lowdb": "5.0.5",
"lunr": "^2.3.9",
"lunr-languages": "^1.9.0",
"mdast-util-from-markdown": "^1.2.0",