Merge pull request #35370 from github/repo-sync

Repo sync
This commit is contained in:
docs-bot 2024-11-20 10:36:27 -08:00 коммит произвёл GitHub
Родитель 5e50aa665c 1ad5033572
Коммит ef684f4d98
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
17 изменённых файлов: 91 добавлений и 95 удалений

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

@ -33,7 +33,7 @@ runs:
- name: Run script - name: Run script
if: ${{ inputs.restore-only == '' }} if: ${{ inputs.restore-only == '' }}
shell: bash shell: bash
run: node src/archives/scripts/warmup-remotejson.js run: npm run warmup-remotejson
- name: Cache .remotejson-cache (save) - name: Cache .remotejson-cache (save)
if: ${{ inputs.restore-only == '' }} if: ${{ inputs.restore-only == '' }}

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

@ -7,7 +7,7 @@ name: Purge old workflow runs
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: '20 */2 * * *' # Run every 2 hours at 20 minutes past the hour - cron: '20 * * * *' # Run every hour at 20 minutes past the hour
permissions: permissions:
contents: write contents: write

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

@ -88,7 +88,7 @@
"update-internal-links": "tsx src/links/scripts/update-internal-links.ts", "update-internal-links": "tsx src/links/scripts/update-internal-links.ts",
"validate-asset-images": "tsx src/assets/scripts/validate-asset-images.ts", "validate-asset-images": "tsx src/assets/scripts/validate-asset-images.ts",
"validate-github-github-docs-urls": "tsx src/links/scripts/validate-github-github-docs-urls/index.ts", "validate-github-github-docs-urls": "tsx src/links/scripts/validate-github-github-docs-urls/index.ts",
"warmup-remotejson": "node src/archives/scripts/warmup-remotejson.js" "warmup-remotejson": "tsx src/archives/scripts/warmup-remotejson.ts"
}, },
"lint-staged": { "lint-staged": {
"*.{js,mjs,ts,tsx}": "eslint --cache --fix", "*.{js,mjs,ts,tsx}": "eslint --cache --fix",

9
src/archives/lib/is-archived-version.d.ts поставляемый
Просмотреть файл

@ -1,9 +0,0 @@
import type { ExtendedRequest } from '@/types'
type IsArchivedInfo = {
isArchived?: boolean
requestedVersion?: string
}
export declare function isArchivedVersion(req: ExtendedRequest): IsArchivedInfo
export declare function isArchivedVersionByPath(pathToCheck: string): IsArchivedInfo

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

@ -1,14 +1,20 @@
import patterns from '#src/frame/lib/patterns.js' import patterns from '@/frame/lib/patterns.js'
import { deprecated } from '#src/versions/lib/enterprise-server-releases.js' import { deprecated } from '@/versions/lib/enterprise-server-releases.js'
import type { ExtendedRequest } from '@/types'
export function isArchivedVersion(req) { type IsArchivedInfo = {
isArchived?: boolean
requestedVersion?: string
}
export function isArchivedVersion(req: ExtendedRequest): IsArchivedInfo {
// if this is an assets path, use the referrer // if this is an assets path, use the referrer
// if this is a docs path, use the req.path // if this is a docs path, use the req.path
const pathToCheck = patterns.assetPaths.test(req.path) ? req.get('referrer') : req.path const pathToCheck = patterns.assetPaths.test(req.path) ? req.get('referrer') : req.path
return isArchivedVersionByPath(pathToCheck || '') return isArchivedVersionByPath(pathToCheck || '')
} }
export function isArchivedVersionByPath(pathToCheck) { export function isArchivedVersionByPath(pathToCheck: string): IsArchivedInfo {
// ignore paths that don't have an enterprise version number // ignore paths that don't have an enterprise version number
if ( if (
!( !(
@ -22,10 +28,10 @@ export function isArchivedVersionByPath(pathToCheck) {
// extract enterprise version from path, e.g. 2.16 // extract enterprise version from path, e.g. 2.16
const requestedVersion = pathToCheck.includes('enterprise-server@') const requestedVersion = pathToCheck.includes('enterprise-server@')
? pathToCheck.match(patterns.getEnterpriseServerNumber)?.[1] ? pathToCheck.match(patterns.getEnterpriseServerNumber)?.[1]
: pathToCheck.match(patterns.getEnterpriseVersionNumber)[1] : pathToCheck.match(patterns.getEnterpriseVersionNumber)?.[1]
// bail if the request version is not deprecated // bail if the request version is not deprecated
if (!deprecated.includes(requestedVersion)) { if (!requestedVersion || !deprecated.includes(requestedVersion)) {
return {} return {}
} }

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

@ -1,8 +1,8 @@
import path from 'path' import path from 'path'
import { supported, latest } from '#src/versions/lib/enterprise-server-releases.js' import { supported, latest } from '@/versions/lib/enterprise-server-releases.js'
import patterns from '#src/frame/lib/patterns.js' import patterns from '@/frame/lib/patterns.js'
import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version.js'
import { allVersions } from '#src/versions/lib/all-versions.js' import { allVersions } from '@/versions/lib/all-versions.js'
const latestNewVersion = `enterprise-server@${latest}` const latestNewVersion = `enterprise-server@${latest}`
const oldVersions = ['dotcom'].concat(supported) const oldVersions = ['dotcom'].concat(supported)
const newVersions = Object.keys(allVersions) const newVersions = Object.keys(allVersions)
@ -18,7 +18,7 @@ const newVersions = Object.keys(allVersions)
// return an old version like 2.21. // return an old version like 2.21.
// Fall back to latest GHES version if one can't be found, // Fall back to latest GHES version if one can't be found,
// for example, if the new version is private-instances@latest. // for example, if the new version is private-instances@latest.
export function getOldVersionFromNewVersion(newVersion) { export function getOldVersionFromNewVersion(newVersion: string) {
return newVersion === nonEnterpriseDefaultVersion return newVersion === nonEnterpriseDefaultVersion
? 'dotcom' ? 'dotcom'
: oldVersions.find((oldVersion) => newVersion.includes(oldVersion)) || latest : oldVersions.find((oldVersion) => newVersion.includes(oldVersion)) || latest
@ -27,7 +27,7 @@ export function getOldVersionFromNewVersion(newVersion) {
// Given an old version like 2.21, // Given an old version like 2.21,
// return a new version like enterprise-server@2.21. // return a new version like enterprise-server@2.21.
// Fall back to latest GHES version if one can't be found. // Fall back to latest GHES version if one can't be found.
export function getNewVersionFromOldVersion(oldVersion) { export function getNewVersionFromOldVersion(oldVersion: string) {
return oldVersion === 'dotcom' return oldVersion === 'dotcom'
? nonEnterpriseDefaultVersion ? nonEnterpriseDefaultVersion
: newVersions.find((newVersion) => newVersion.includes(oldVersion)) || latestNewVersion : newVersions.find((newVersion) => newVersion.includes(oldVersion)) || latestNewVersion
@ -35,7 +35,7 @@ export function getNewVersionFromOldVersion(oldVersion) {
// Given an old path like /enterprise/2.21/user/github/category/article, // Given an old path like /enterprise/2.21/user/github/category/article,
// return an old version like 2.21. // return an old version like 2.21.
export function getOldVersionFromOldPath(oldPath) { export function getOldVersionFromOldPath(oldPath: string) {
// We should never be calling this function on a path that starts with a new version, // We should never be calling this function on a path that starts with a new version,
// so we can assume the path either uses the old /enterprise format or it's dotcom. // so we can assume the path either uses the old /enterprise format or it's dotcom.
if (!patterns.enterprise.test(oldPath)) return 'dotcom' if (!patterns.enterprise.test(oldPath)) return 'dotcom'
@ -46,7 +46,7 @@ export function getOldVersionFromOldPath(oldPath) {
// Given an old path like /en/enterprise/2.21/user/github/category/article, // Given an old path like /en/enterprise/2.21/user/github/category/article,
// return a new path like /en/enterprise-server@2.21/github/category/article. // return a new path like /en/enterprise-server@2.21/github/category/article.
export function getNewVersionedPath(oldPath, languageCode = '') { export function getNewVersionedPath(oldPath: string, languageCode = '') {
// It's possible a new version has been injected into an old path // It's possible a new version has been injected into an old path
// via syntax like: /en/enterprise/{{ currentVersion }}/admin/category/article // via syntax like: /en/enterprise/{{ currentVersion }}/admin/category/article
// which could resolve to /en/enterprise/private-instances@latest/admin/category/article, // which could resolve to /en/enterprise/private-instances@latest/admin/category/article,
@ -58,7 +58,7 @@ export function getNewVersionedPath(oldPath, languageCode = '') {
// If no new version was found, assume path contains an old version, like 2.21 // If no new version was found, assume path contains an old version, like 2.21
if (!newVersion) { if (!newVersion) {
const oldVersion = getOldVersionFromOldPath(oldPath, languageCode) const oldVersion = getOldVersionFromOldPath(oldPath)
newVersion = getNewVersionFromOldVersion(oldVersion) newVersion = getNewVersionFromOldVersion(oldVersion)
} }

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

@ -2,7 +2,7 @@ import got from 'got'
import type { Response, NextFunction } from 'express' import type { Response, NextFunction } from 'express'
import patterns from '@/frame/lib/patterns.js' import patterns from '@/frame/lib/patterns.js'
import { isArchivedVersion } from '@/archives/lib/is-archived-version.js' import { isArchivedVersion } from '@/archives/lib/is-archived-version'
import { import {
setFastlySurrogateKey, setFastlySurrogateKey,
SURROGATE_ENUMS, SURROGATE_ENUMS,

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

@ -10,7 +10,7 @@ import {
} from '@/versions/lib/enterprise-server-releases.js' } from '@/versions/lib/enterprise-server-releases.js'
import patterns from '@/frame/lib/patterns.js' import patterns from '@/frame/lib/patterns.js'
import versionSatisfiesRange from '@/versions/lib/version-satisfies-range.js' import versionSatisfiesRange from '@/versions/lib/version-satisfies-range.js'
import { isArchivedVersion } from '@/archives/lib/is-archived-version.js' import { isArchivedVersion } from '@/archives/lib/is-archived-version'
import { import {
setFastlySurrogateKey, setFastlySurrogateKey,
SURROGATE_ENUMS, SURROGATE_ENUMS,

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

@ -19,12 +19,11 @@
// [end-readme] // [end-readme]
import { program } from 'commander' import { program } from 'commander'
import semver from 'semver' import semver, { SemVer } from 'semver'
import getRemoteJSON from '#src/frame/lib/get-remote-json.js' import getRemoteJSON from '@/frame/lib/get-remote-json.js'
import { import {
deprecated, deprecated,
firstReleaseStoredInBlobStorage,
lastVersionWithoutArchivedRedirectsFile, lastVersionWithoutArchivedRedirectsFile,
} from '#src/versions/lib/enterprise-server-releases.js' } from '#src/versions/lib/enterprise-server-releases.js'
@ -36,18 +35,14 @@ program
main() main()
function version2url(version) { function version2url(version: string | SemVer) {
const inBlobStorage = semver.gte(
semver.coerce(version).raw,
semver.coerce(firstReleaseStoredInBlobStorage).raw,
)
return `https://github.github.com/docs-ghes-${version}/redirects.json` return `https://github.github.com/docs-ghes-${version}/redirects.json`
} }
function withArchivedRedirectsFile(version) { function withArchivedRedirectsFile(version: string | SemVer) {
return semver.eq( return semver.eq(
semver.coerce(version).raw, semver.coerce(version)?.raw || '',
semver.coerce(lastVersionWithoutArchivedRedirectsFile).raw, semver.coerce(lastVersionWithoutArchivedRedirectsFile)?.raw || '',
) )
} }

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

@ -1,8 +1,8 @@
import { describe, expect, test, vi } from 'vitest' import { describe, expect, test, vi } from 'vitest'
import enterpriseServerReleases from '#src/versions/lib/enterprise-server-releases.js' import enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.js'
import { get, getDOM } from '#src/tests/helpers/e2etest.js' import { get, getDOM } from '@/tests/helpers/e2etest-ts'
import { SURROGATE_ENUMS } from '#src/frame/middleware/set-fastly-surrogate-key.js' import { SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key.js'
describe('enterprise deprecation', () => { describe('enterprise deprecation', () => {
vi.setConfig({ testTimeout: 60 * 1000 }) vi.setConfig({ testTimeout: 60 * 1000 })
@ -45,8 +45,8 @@ describe('enterprise deprecation', () => {
test('handles requests for deprecated Enterprise pages ( >=2.13 )', async () => { test('handles requests for deprecated Enterprise pages ( >=2.13 )', async () => {
expect(enterpriseServerReleases.deprecated.includes('2.13')).toBe(true) expect(enterpriseServerReleases.deprecated.includes('2.13')).toBe(true)
const $ = await getDOM('/en/enterprise/2.13/user/articles/about-branches') const { $, res } = await getDOM('/en/enterprise/2.13/user/articles/about-branches')
expect($.res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
expect($('h1').first().text()).toBe('About branches') expect($('h1').first().text()).toBe('About branches')
}) })
@ -60,27 +60,27 @@ describe('enterprise deprecation', () => {
test('handles requests for deprecated Enterprise pages ( <2.13 )', async () => { test('handles requests for deprecated Enterprise pages ( <2.13 )', async () => {
expect(enterpriseServerReleases.deprecated.includes('2.12')).toBe(true) expect(enterpriseServerReleases.deprecated.includes('2.12')).toBe(true)
const $ = await getDOM('/enterprise/2.12/user/articles/about-branches') const { $, res } = await getDOM('/enterprise/2.12/user/articles/about-branches')
expect($.res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
expect($('h2').text()).toBe('About branches') expect($('h2').text()).toBe('About branches')
}) })
test('handles requests for deprecated Enterprise version 11.10.340', async () => { test('handles requests for deprecated Enterprise version 11.10.340', async () => {
expect(enterpriseServerReleases.deprecated.includes('11.10.340')).toBe(true) expect(enterpriseServerReleases.deprecated.includes('11.10.340')).toBe(true)
const $ = await getDOM('/enterprise/11.10.340/admin/articles/adding-teams') const { $, res } = await getDOM('/enterprise/11.10.340/admin/articles/adding-teams')
expect($.res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
expect($('h2').text()).toBe('Adding teams') expect($('h2').text()).toBe('Adding teams')
}) })
test('has working admin guide links ( <2.13 )', async () => { test('has working admin guide links ( <2.13 )', async () => {
const guidesPath = '/enterprise/2.12/admin' const guidesPath = '/enterprise/2.12/admin'
let $ = await getDOM(`${guidesPath}/guides`) const { $: $1 } = await getDOM(`${guidesPath}/guides`)
const firstLink = $('[class="guide-section"]').children('a').attr('href') const firstLink = $1('[class="guide-section"]').children('a').attr('href')
$ = await getDOM(`${guidesPath}/${firstLink}`) const { $: $2, res } = await getDOM(`${guidesPath}/${firstLink}`)
expect($.res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
// this test assumes the Installation guide is the first link on the guides page // this test assumes the Installation guide is the first link on the guides page
expect($('h2').text()).toBe('Installing and configuring GitHub Enterprise') expect($2('h2').text()).toBe('Installing and configuring GitHub Enterprise')
}) })
}) })
@ -139,22 +139,22 @@ describe('recently deprecated redirects', () => {
describe('deprecation banner', () => { describe('deprecation banner', () => {
test('renders a deprecation warning banner on oldest supported Enterprise version', async () => { test('renders a deprecation warning banner on oldest supported Enterprise version', async () => {
const $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.oldestSupported}`) const { $ } = await getDOM(`/en/enterprise/${enterpriseServerReleases.oldestSupported}`)
expect($('[data-testid=deprecation-banner]').length).toBe(1) expect($('[data-testid=deprecation-banner]').length).toBe(1)
}) })
test('does not render a deprecation warning banner on other Enterprise versions', async () => { test('does not render a deprecation warning banner on other Enterprise versions', async () => {
const $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}`) const { $ } = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}`)
expect($('[data-testid=deprecation-banner]').length).toBe(0) expect($('[data-testid=deprecation-banner]').length).toBe(0)
}) })
test('deprecation warning banner includes a date', async () => { test('deprecation warning banner includes a date', async () => {
const $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.oldestSupported}`) const { $ } = await getDOM(`/en/enterprise/${enterpriseServerReleases.oldestSupported}`)
expect($('[data-testid=deprecation-banner] b').text().endsWith('discontinued on .')).toBe(false) expect($('[data-testid=deprecation-banner] b').text().endsWith('discontinued on .')).toBe(false)
}) })
test('deprecation warning banner includes the right text depending on the date', async () => { test('deprecation warning banner includes the right text depending on the date', async () => {
const $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.oldestSupported}`) const { $ } = await getDOM(`/en/enterprise/${enterpriseServerReleases.oldestSupported}`)
const expectedString = enterpriseServerReleases.isOldestReleaseDeprecated const expectedString = enterpriseServerReleases.isOldestReleaseDeprecated
? 'was discontinued' ? 'was discontinued'
: 'will be discontinued' : 'will be discontinued'
@ -164,24 +164,28 @@ describe('deprecation banner', () => {
describe('does not render survey prompt or contribution button', () => { describe('does not render survey prompt or contribution button', () => {
test('does not render survey prompt', async () => { test('does not render survey prompt', async () => {
let $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}/github`) const { $: $1 } = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}/github`)
expect($('[data-testid="survey-form"]').length).toBeGreaterThan(0) expect($1('[data-testid="survey-form"]').length).toBeGreaterThan(0)
$ = await getDOM(`/en/enterprise/${enterpriseServerReleases.oldestSupported}/github`) const { $: $2 } = await getDOM(
`/en/enterprise/${enterpriseServerReleases.oldestSupported}/github`,
)
if (enterpriseServerReleases.isOldestReleaseDeprecated) { if (enterpriseServerReleases.isOldestReleaseDeprecated) {
expect($('[data-testid="survey-form"]').length).toBe(0) expect($2('[data-testid="survey-form"]').length).toBe(0)
} else { } else {
expect($('[data-testid="survey-form"]').length).toBeGreaterThan(0) expect($2('[data-testid="survey-form"]').length).toBeGreaterThan(0)
} }
}) })
test('does not render contribution button', async () => { test('does not render contribution button', async () => {
let $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}/github`) const { $: $1 } = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}/github`)
expect($('.contribution').length).toBeGreaterThan(0) expect($1('.contribution').length).toBeGreaterThan(0)
$ = await getDOM(`/en/enterprise/${enterpriseServerReleases.oldestSupported}/github`) const { $: $2 } = await getDOM(
`/en/enterprise/${enterpriseServerReleases.oldestSupported}/github`,
)
if (enterpriseServerReleases.isOldestReleaseDeprecated) { if (enterpriseServerReleases.isOldestReleaseDeprecated) {
expect($('.contribution').length).toBe(0) expect($2('.contribution').length).toBe(0)
} else { } else {
expect($('[data-testid=survey-form]').length).toBeGreaterThan(0) expect($2('[data-testid=survey-form]').length).toBeGreaterThan(0)
} }
}) })
}) })

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

@ -5,7 +5,7 @@ import stripAnsi from 'strip-ansi'
import { visit } from 'unist-util-visit' import { visit } from 'unist-util-visit'
import { distance } from 'fastest-levenshtein' import { distance } from 'fastest-levenshtein'
import { getPathWithoutLanguage, getVersionStringFromPath } from '#src/frame/lib/path-utils.js' import { getPathWithoutLanguage, getVersionStringFromPath } from '#src/frame/lib/path-utils.js'
import { getNewVersionedPath } from '#src/archives/lib/old-versions-utils.js' import { getNewVersionedPath } from '#src/archives/lib/old-versions-utils.ts'
import patterns from '#src/frame/lib/patterns.js' import patterns from '#src/frame/lib/patterns.js'
import { deprecated, latest } from '#src/versions/lib/enterprise-server-releases.js' import { deprecated, latest } from '#src/versions/lib/enterprise-server-releases.js'
import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js' import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js'

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

@ -1,6 +1,6 @@
import type { NextFunction, Request, Response } from 'express' import type { NextFunction, Request, Response } from 'express'
import helmet from 'helmet' import helmet from 'helmet'
import { isArchivedVersion } from '@/archives/lib/is-archived-version.js' import { isArchivedVersion } from '@/archives/lib/is-archived-version'
import versionSatisfiesRange from '@/versions/lib/version-satisfies-range.js' import versionSatisfiesRange from '@/versions/lib/version-satisfies-range.js'
import { languagePrefixPathRegex } from '@/languages/lib/languages.js' import { languagePrefixPathRegex } from '@/languages/lib/languages.js'

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

@ -14,7 +14,7 @@ import shortVersions from '@/versions/middleware/short-versions.js'
import contextualize from '@/frame/middleware/context/context' import contextualize from '@/frame/middleware/context/context'
import features from '@/versions/middleware/features.js' import features from '@/versions/middleware/features.js'
import getRedirect from '@/redirects/lib/get-redirect.js' import getRedirect from '@/redirects/lib/get-redirect.js'
import { isArchivedVersionByPath } from '@/archives/lib/is-archived-version.js' import { isArchivedVersionByPath } from '@/archives/lib/is-archived-version'
import { readCompressedJsonFile } from '@/frame/lib/read-json-file.js' import { readCompressedJsonFile } from '@/frame/lib/read-json-file.js'
const router = express.Router() const router = express.Router()

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

@ -32,7 +32,7 @@ describeIfElasticsearchURL('search rendering page', () => {
// To see why this will work, // To see why this will work,
// see src/search/tests/fixtures/search-indexes/github-docs-dotcom-en-records.json // see src/search/tests/fixtures/search-indexes/github-docs-dotcom-en-records.json
// which clearly has a record with the title "Foo" // which clearly has a record with the title "Foo"
const $ = await getDOM('/en/search?query=foo') const { $ } = await getDOM('/en/search?query=foo')
expect($('h1').text()).toMatch(/\d+ Search results for "foo"/) expect($('h1').text()).toMatch(/\d+ Search results for "foo"/)
// Note it testid being 'search-result', not 'search-results' // Note it testid being 'search-result', not 'search-results'
@ -62,7 +62,7 @@ describeIfElasticsearchURL('search rendering page', () => {
}) })
test('debug search', async () => { test('debug search', async () => {
const $ = await getDOM('/en/search?query=foo&debug=1') const { $ } = await getDOM('/en/search?query=foo&debug=1')
expect($('h1').text()).toMatch(/\d+ Search results for "foo"/) expect($('h1').text()).toMatch(/\d+ Search results for "foo"/)
// Note it testid being 'search-result', not 'search-results' // Note it testid being 'search-result', not 'search-results'
@ -74,21 +74,21 @@ describeIfElasticsearchURL('search rendering page', () => {
}) })
test('no query', async () => { test('no query', async () => {
const $ = await getDOM('/en/search') const { $ } = await getDOM('/en/search')
const results = $('[data-testid="search-result"]') const results = $('[data-testid="search-result"]')
expect(results.length).toBe(0) expect(results.length).toBe(0)
expect($('[data-testid="search-results"]').text()).toMatch('Enter a search term') expect($('[data-testid="search-results"]').text()).toMatch('Enter a search term')
}) })
test('empty query', async () => { test('empty query', async () => {
const $ = await getDOM('/en/search?query=') const { $ } = await getDOM('/en/search?query=')
const results = $('[data-testid="search-result"]') const results = $('[data-testid="search-result"]')
expect(results.length).toBe(0) expect(results.length).toBe(0)
expect($('[data-testid="search-results"]').text()).toMatch('Enter a search term') expect($('[data-testid="search-results"]').text()).toMatch('Enter a search term')
}) })
test('find nothing', async () => { test('find nothing', async () => {
const $ = await getDOM('/en/search?query=xojixjoiwejhfoiuwehjfioweufhj') const { $ } = await getDOM('/en/search?query=xojixjoiwejhfoiuwehjfioweufhj')
const results = $('[data-testid="search-result"]') const results = $('[data-testid="search-result"]')
expect(results.length).toBe(0) expect(results.length).toBe(0)
expect($('[data-testid="search-results"]').text()).toMatch('0 Search results') expect($('[data-testid="search-results"]').text()).toMatch('0 Search results')
@ -100,7 +100,7 @@ describeIfElasticsearchURL('search rendering page', () => {
}) })
test('links per version in pathname', async () => { test('links per version in pathname', async () => {
const $ = await getDOM('/en/enterprise-cloud@latest/search?query=foo') const { $ } = await getDOM('/en/enterprise-cloud@latest/search?query=foo')
expect($('[data-testid="search-results"]').text()).toMatch('Exclusively for GHEC') expect($('[data-testid="search-results"]').text()).toMatch('Exclusively for GHEC')
// Note it testid being 'search-result', not 'search-results' // Note it testid being 'search-result', not 'search-results'
const results = $('[data-testid="search-result"]') const results = $('[data-testid="search-result"]')
@ -115,14 +115,14 @@ describeIfElasticsearchURL('search rendering page', () => {
}) })
test('invalid parameters (page)', async () => { test('invalid parameters (page)', async () => {
const $ = await getDOM('/en/search?query=foo&page=999') const { $ } = await getDOM('/en/search?query=foo&page=999')
expect($('[data-testid="search-results"]').text()).toMatch('Not a valid value (999)') expect($('[data-testid="search-results"]').text()).toMatch('Not a valid value (999)')
const results = $('[data-testid="search-result"]') const results = $('[data-testid="search-result"]')
expect(results.length).toBe(0) expect(results.length).toBe(0)
}) })
test('invalid parameters (size)', async () => { test('invalid parameters (size)', async () => {
const $ = await getDOM('/en/search?query=foo&size=888') const { $ } = await getDOM('/en/search?query=foo&size=888')
expect($('[data-testid="search-results"]').text()).toMatch('Not a valid value (888)') expect($('[data-testid="search-results"]').text()).toMatch('Not a valid value (888)')
const results = $('[data-testid="search-result"]') const results = $('[data-testid="search-result"]')
expect(results.length).toBe(0) expect(results.length).toBe(0)
@ -134,14 +134,14 @@ describeIfElasticsearchURL('search rendering page', () => {
}) })
test('more than one search query', async () => { test('more than one search query', async () => {
const $ = await getDOM('/en/search?query=foo&query=bar') const { $ } = await getDOM('/en/search?query=foo&query=bar')
expect($('[data-testid="search-results"]').text()).toMatch('Cannot have multiple values') expect($('[data-testid="search-results"]').text()).toMatch('Cannot have multiple values')
const results = $('[data-testid="search-result"]') const results = $('[data-testid="search-result"]')
expect(results.length).toBe(0) expect(results.length).toBe(0)
}) })
test("search with 'toplevel' query string", async () => { test("search with 'toplevel' query string", async () => {
const $ = await getDOM('/en/search?query=foo&toplevel=Baring') const { $ } = await getDOM('/en/search?query=foo&toplevel=Baring')
expect($('h1').text()).toMatch(/\d+ Search results for "foo"/) expect($('h1').text()).toMatch(/\d+ Search results for "foo"/)
// Note it testid being 'search-result', not 'search-results' // Note it testid being 'search-result', not 'search-results'

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

@ -5,7 +5,7 @@ describe('search results page', () => {
vi.setConfig({ testTimeout: 60 * 1000 }) vi.setConfig({ testTimeout: 60 * 1000 })
test('says something if no query is provided', async (): Promise<void> => { test('says something if no query is provided', async (): Promise<void> => {
const $ = await getDOM('/en/search') const { $ } = await getDOM('/en/search')
const $container = $('[data-testid="search-results"]') const $container = $('[data-testid="search-results"]')
expect($container.text()).toMatch(/Enter a search term/) expect($container.text()).toMatch(/Enter a search term/)
// Default is the frontmatter title of the content/search/index.md // Default is the frontmatter title of the content/search/index.md
@ -14,7 +14,7 @@ describe('search results page', () => {
test('says something if query is empty', async (): Promise<void> => { test('says something if query is empty', async (): Promise<void> => {
const queryParams = new URLSearchParams({ query: ' ' }).toString() const queryParams = new URLSearchParams({ query: ' ' }).toString()
const $ = await getDOM(`/en/search?${queryParams}`) const { $ } = await getDOM(`/en/search?${queryParams}`)
const $container = $('[data-testid="search-results"]') const $container = $('[data-testid="search-results"]')
expect($container.text()).toMatch(/Enter a search term/) expect($container.text()).toMatch(/Enter a search term/)
}) })
@ -22,7 +22,7 @@ describe('search results page', () => {
test('mentions search term in h1', async (): Promise<void> => { test('mentions search term in h1', async (): Promise<void> => {
const searchTerm = 'peterbe' const searchTerm = 'peterbe'
const queryParams = new URLSearchParams({ query: searchTerm }).toString() const queryParams = new URLSearchParams({ query: searchTerm }).toString()
const $ = await getDOM(`/en/search?${queryParams}`) const { $ } = await getDOM(`/en/search?${queryParams}`)
const $container = $('[data-testid="search-results"]') const $container = $('[data-testid="search-results"]')
const h1Text: string = $container.find('h1').text() const h1Text: string = $container.find('h1').text()

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

@ -122,8 +122,8 @@ export async function getDOMCached(
): Promise<cheerio.Root> { ): Promise<cheerio.Root> {
const key = `${route}::${JSON.stringify(options)}` const key = `${route}::${JSON.stringify(options)}`
if (!getDOMCache.has(key)) { if (!getDOMCache.has(key)) {
const dom = await getDOM(route, options) const { $ } = await getDOM(route, options)
getDOMCache.set(key, dom) getDOMCache.set(key, $)
} }
// The non-null assertion is safe here because we've just set the key if it didn't exist // The non-null assertion is safe here because we've just set the key if it didn't exist
return getDOMCache.get(key)! return getDOMCache.get(key)!
@ -136,7 +136,10 @@ export async function getDOMCached(
* @param options - Options for fetching the DOM. * @param options - Options for fetching the DOM.
* @returns A promise that resolves to the loaded DOM object. * @returns A promise that resolves to the loaded DOM object.
*/ */
export async function getDOM(route: string, options: GetDOMOptions = {}): Promise<cheerio.Root> { export async function getDOM(
route: string,
options: GetDOMOptions = {},
): Promise<{ $: cheerio.Root; res: Response }> {
const { headers, allow500s = false, allow404 = false, retries = 0 } = options const { headers, allow500s = false, allow404 = false, retries = 0 } = options
const res = await get(route, { followRedirects: true, headers, retries }) const res = await get(route, { followRedirects: true, headers, retries })
@ -150,10 +153,7 @@ export async function getDOM(route: string, options: GetDOMOptions = {}): Promis
const $ = cheerio.load(res.body || '', { xmlMode: true }) const $ = cheerio.load(res.body || '', { xmlMode: true })
// Extend the Cheerio instance with the response object return { $, res }
;($ as any).res = { ...res }
return $
} }
/** /**

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

@ -18,7 +18,7 @@
* For every run found, it deletes its logs and its run. * For every run found, it deletes its logs and its run.
* *
* The total number of deletions is limited by the `MAX_DELETIONS` * The total number of deletions is limited by the `MAX_DELETIONS`
* environment variable. The default is 100. * environment variable. The default is 2000.
* */ * */
import fs from 'fs' import fs from 'fs'
@ -29,7 +29,7 @@ import { getOctokit } from '@actions/github'
main() main()
async function main() { async function main() {
const DRY_RUN = Boolean(JSON.parse(process.env.DRY_RUN || 'false')) const DRY_RUN = Boolean(JSON.parse(process.env.DRY_RUN || 'false'))
const MAX_DELETIONS = parseInt(JSON.parse(process.env.MAX_DELETIONS || '100')) const MAX_DELETIONS = parseInt(JSON.parse(process.env.MAX_DELETIONS || '2000'))
const MIN_AGE_DAYS = parseInt(process.env.MIN_AGE_DAYS || '90', 10) const MIN_AGE_DAYS = parseInt(process.env.MIN_AGE_DAYS || '90', 10)
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/')
@ -134,11 +134,11 @@ async function deleteWorkflowRuns(
owner, owner,
repo, repo,
workflow, workflow,
{ dryRun = false, minAgeDays = 100, maxDeletions = 1000 }, { dryRun = false, minAgeDays = 90, maxDeletions = 2000 },
) { ) {
// https://docs.github.com/en/search-github/getting-started-with-searching-on-github/understanding-the-search-syntax#query-for-dates // https://docs.github.com/en/search-github/getting-started-with-searching-on-github/understanding-the-search-syntax#query-for-dates
const minCreated = new Date(Date.now() - minAgeDays * 24 * 60 * 60 * 1000) const minCreated = new Date(Date.now() - minAgeDays * 24 * 60 * 60 * 1000)
const minCreatedSearch = `<=${minCreated.toISOString().split('T')[0]}` const minCreatedSearch = `<${minCreated.toISOString().split('T')[0]}`
// Delete is 10, but max is 100. But if we're only going to delete, // Delete is 10, but max is 100. But if we're only going to delete,
// 30, use 30. And if we're only going to delete 5, use the default // 30, use 30. And if we're only going to delete 5, use the default
// per_page value of 10. // per_page value of 10.