Skip to content

Commit 6ca0794

Browse files
authored
Port whats-new-changelog.js to TypeScript (#51199)
1 parent d0b8171 commit 6ca0794

File tree

5 files changed

+56
-20
lines changed

5 files changed

+56
-20
lines changed

src/changelogs/lib/changelog.js renamed to src/changelogs/lib/changelog.ts

+23-12
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,32 @@ import path from 'path'
44

55
import Parser from 'rss-parser'
66

7+
import type { ChangelogItem } from '@/types'
8+
79
const CHANGELOG_CACHE_FILE_PATH = process.env.CHANGELOG_CACHE_FILE_PATH
810
// This is useful to set when doing things like sync search.
911
const CHANGELOG_DISABLED = Boolean(JSON.parse(process.env.CHANGELOG_DISABLED || 'false'))
1012

11-
async function getRssFeed(url) {
13+
async function getRssFeed(url: string) {
1214
const parser = new Parser({ timeout: 5000 })
1315
const feedUrl = `${url}/feed`
1416
let feed
1517

1618
try {
1719
feed = await parser.parseURL(feedUrl)
1820
} catch (err) {
19-
console.error(`cannot get ${feedUrl}: ${err.message}`)
21+
console.error(`cannot get ${feedUrl}: ${err instanceof Error ? err.message : err}`)
2022
return
2123
}
2224

2325
return feed
2426
}
2527

26-
export async function getChangelogItems(prefix, feedUrl, ignoreCache = false) {
28+
export async function getChangelogItems(
29+
prefix: string | undefined,
30+
feedUrl: string,
31+
ignoreCache = false,
32+
): Promise<ChangelogItem[] | undefined> {
2733
if (CHANGELOG_DISABLED) {
2834
if (process.env.NODE_ENV === 'development') {
2935
console.warn(`Downloading changelog (${feedUrl}) items is disabled.`)
@@ -44,14 +50,15 @@ export async function getChangelogItems(prefix, feedUrl, ignoreCache = false) {
4450
}
4551

4652
// only show the first 3 posts
47-
const changelog = feed.items.slice(0, 3).map((item) => {
53+
const changelog: ChangelogItem[] = feed.items.slice(0, 3).map((item) => {
54+
const rawTitle = item.title as string
4855
// remove the prefix if it exists (Ex: 'GitHub Actions: '), where the colon and expected whitespace should be hardcoded.
49-
const title = prefix ? item.title.replace(new RegExp(`^${prefix}`), '') : item.title
56+
const title = prefix ? rawTitle.replace(new RegExp(`^${prefix}`), '') : rawTitle
5057
return {
5158
// capitalize the first letter of the title
5259
title: title.trim().charAt(0).toUpperCase() + title.slice(1),
53-
date: item.isoDate,
54-
href: item.link,
60+
date: item.isoDate as string,
61+
href: item.link as string,
5562
}
5663
})
5764

@@ -65,13 +72,13 @@ export async function getChangelogItems(prefix, feedUrl, ignoreCache = false) {
6572

6673
const globalCache = new Map()
6774

68-
function getChangelogCacheKey(prefix, feedUrl) {
75+
function getChangelogCacheKey(prefix: string | undefined, feedUrl: string) {
6976
// Return a string that is only letters so it's safe to use this
7077
// for the filename when caching to disk.
7178
return `${prefix || ''}${feedUrl}`.replace(/[^a-z]+/gi, '')
7279
}
7380

74-
function getDiskCachePath(prefix, feedUrl) {
81+
function getDiskCachePath(prefix: string | undefined, feedUrl: string) {
7582
// When in local development or in tests, use disk caching
7683
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') {
7784
if (CHANGELOG_CACHE_FILE_PATH) {
@@ -84,7 +91,7 @@ function getDiskCachePath(prefix, feedUrl) {
8491
}
8592
}
8693

87-
function getChangelogItemsFromCache(prefix, feedUrl) {
94+
function getChangelogItemsFromCache(prefix: string | undefined, feedUrl: string) {
8895
const cacheKey = getChangelogCacheKey(prefix, feedUrl)
8996

9097
if (globalCache.get(cacheKey)) {
@@ -103,7 +110,7 @@ function getChangelogItemsFromCache(prefix, feedUrl) {
103110
return payload
104111
} catch (err) {
105112
// If it wasn't on disk, that's fine.
106-
if (err.code === 'ENOENT') return
113+
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') return
107114
// The JSON.parse() most likely failed. Ignore the error
108115
// but delete the file so it won't be attempted again.
109116
if (err instanceof SyntaxError) {
@@ -115,7 +122,11 @@ function getChangelogItemsFromCache(prefix, feedUrl) {
115122
}
116123
}
117124

118-
function setChangelogItemsCache(prefix, feedUrl, payload) {
125+
function setChangelogItemsCache(
126+
prefix: string | undefined,
127+
feedUrl: string,
128+
payload: ChangelogItem[],
129+
) {
119130
const cacheKey = getChangelogCacheKey(prefix, feedUrl)
120131
globalCache.set(cacheKey, payload)
121132

src/changelogs/tests/get-rss-feeds.js renamed to src/changelogs/tests/get-rss-feeds.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import path from 'path'
44
import nock from 'nock'
55
import { afterAll, beforeAll, describe, expect, test } from 'vitest'
66

7-
import { getChangelogItems } from '#src/changelogs/lib/changelog.js'
7+
import { getChangelogItems } from '@/changelogs/lib/changelog'
8+
import type { ChangelogItem } from '@/types'
89

910
describe('getChangelogItems module', () => {
10-
let changelog
11+
let changelog: ChangelogItem[] | undefined
1112

1213
beforeAll(async () => {
1314
const rssFeedContent = await fs.readFile(
@@ -35,7 +36,7 @@ describe('getChangelogItems module', () => {
3536
afterAll(() => nock.cleanAll())
3637

3738
test('changelog contains 3 items', async () => {
38-
expect(changelog.length).toEqual(3)
39+
expect(changelog && changelog.length).toEqual(3)
3940
})
4041

4142
test('each changelog item has expected title, date, and href', async () => {
@@ -57,6 +58,7 @@ describe('getChangelogItems module', () => {
5758
},
5859
]
5960

61+
if (!changelog) throw new Error('changelog is undefined')
6062
for (let i = 0; i < 3; i++) {
6163
const changeLogEntry = changelog[i]
6264
const expectedEntry = expectedChangelogValues[i]

src/frame/middleware/context/whats-new-changelog.js renamed to src/frame/middleware/context/whats-new-changelog.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import { getChangelogItems } from '#src/changelogs/lib/changelog.js'
2-
import getApplicableVersions from '#src/versions/lib/get-applicable-versions.js'
1+
import type { Response, NextFunction } from 'express'
32

4-
export default async function whatsNewChangelog(req, res, next) {
3+
import { getChangelogItems } from '@/changelogs/lib/changelog.js'
4+
import getApplicableVersions from '@/versions/lib/get-applicable-versions.js'
5+
import type { ExtendedRequest } from '@/types'
6+
7+
export default async function whatsNewChangelog(
8+
req: ExtendedRequest,
9+
res: Response,
10+
next: NextFunction,
11+
) {
12+
if (!req.context) throw new Error('request not contextualized')
513
if (!req.context.page) return next()
614
if (!req.context.page.changelog) return next()
715
const label = req.context.page.changelog.label.split(/\s+/g).join('')
@@ -16,7 +24,7 @@ export default async function whatsNewChangelog(req, res, next) {
1624
}
1725
}
1826

19-
const labelUrls = {
27+
const labelUrls: Record<string, string> = {
2028
education: 'https://github.blog/category/community/education',
2129
enterprise: 'https://github.blog/category/enterprise/',
2230
}

src/frame/middleware/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import categoriesForSupport from './categories-for-support'
3838
import triggerError from '@/observability/middleware/trigger-error'
3939
import secretScanning from '@/secret-scanning/middleware/secret-scanning'
4040
import ghesReleaseNotes from '@/release-notes/middleware/ghes-release-notes'
41-
import whatsNewChangelog from './context/whats-new-changelog.js'
41+
import whatsNewChangelog from './context/whats-new-changelog'
4242
import layout from './context/layout.js'
4343
import currentProductTree from './context/current-product-tree.js'
4444
import genericToc from './context/generic-toc.js'

src/types.ts

+15
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export type Context = {
7474
languages?: Languages
7575
redirectNotFound?: string
7676
earlyAccessPageLinks?: string
77+
changelogUrl?: string
78+
whatsNewChangelog?: ChangelogItem[]
7779
secretScanningData?: SecretScanningData[]
7880
ghesReleases?: GHESRelease[]
7981
ghesReleaseNotes?: GHESReleasePatch[]
@@ -123,6 +125,12 @@ export type ReleaseNotes = {
123125
}
124126
}
125127

128+
export type ChangelogItem = {
129+
title: string
130+
date: string
131+
href: string
132+
}
133+
126134
export type SecretScanningData = {
127135
provider: string
128136
supportedSecret: string
@@ -176,6 +184,13 @@ export type Page = {
176184
markdown: string
177185
versions: FrontmatterVersions
178186
applicableVersions: string[]
187+
changelog?: ChangeLog
188+
}
189+
190+
type ChangeLog = {
191+
label: string
192+
prefix?: string
193+
versions?: FrontmatterVersions
179194
}
180195

181196
export type Tree = {

0 commit comments

Comments
 (0)