forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchangelog.js
130 lines (111 loc) · 4.15 KB
/
changelog.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import os from 'os'
import fs from 'fs'
import path from 'path'
import Parser from 'rss-parser'
const CHANGELOG_CACHE_FILE_PATH = process.env.CHANGELOG_CACHE_FILE_PATH
// This is useful to set when doing things like sync search.
const CHANGELOG_DISABLED = Boolean(JSON.parse(process.env.CHANGELOG_DISABLED || 'false'))
async function getRssFeed(url) {
const parser = new Parser({ timeout: 5000 })
const feedUrl = `${url}/feed`
let feed
try {
feed = await parser.parseURL(feedUrl)
} catch (err) {
console.error(`cannot get ${feedUrl}: ${err.message}`)
return
}
return feed
}
export async function getChangelogItems(prefix, feedUrl, ignoreCache = false) {
if (CHANGELOG_DISABLED) {
if (process.env.NODE_ENV === 'development') {
console.warn(`Downloading changelog (${feedUrl}) items is disabled.`)
}
return
}
if (!ignoreCache) {
const fromCache = getChangelogItemsFromCache(prefix, feedUrl)
if (fromCache) return fromCache
}
const feed = await getRssFeed(feedUrl)
if (!feed || !feed.items) {
console.log(feed)
console.error('feed is not valid or has no items')
return
}
// only show the first 3 posts
const changelog = feed.items.slice(0, 3).map((item) => {
// remove the prefix if it exists (Ex: 'GitHub Actions: '), where the colon and expected whitespace should be hardcoded.
const title = prefix ? item.title.replace(new RegExp(`^${prefix}`), '') : item.title
return {
// capitalize the first letter of the title
title: title.trim().charAt(0).toUpperCase() + title.slice(1),
date: item.isoDate,
href: item.link,
}
})
// We don't cache the raw payload we'd get from the network request
// because it would waste memory. Instead we store the "serialized"
// object that's created from the raw payload.
setChangelogItemsCache(prefix, feedUrl, changelog)
return changelog
}
const globalCache = new Map()
function getChangelogCacheKey(prefix, feedUrl) {
// Return a string that is only letters so it's safe to use this
// for the filename when caching to disk.
return `${prefix || ''}${feedUrl}`.replace(/[^a-z]+/gi, '')
}
function getDiskCachePath(prefix, feedUrl) {
// When in local development or in tests, use disk caching
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') {
if (CHANGELOG_CACHE_FILE_PATH) {
return CHANGELOG_CACHE_FILE_PATH
}
const cacheKey = getChangelogCacheKey(prefix, feedUrl)
const date = new Date().toISOString().split('T')[0]
const fileName = `changelogcache-${cacheKey}-${date}.json`
return path.join(os.tmpdir(), fileName)
}
}
function getChangelogItemsFromCache(prefix, feedUrl) {
const cacheKey = getChangelogCacheKey(prefix, feedUrl)
if (globalCache.get(cacheKey)) {
return globalCache.get(cacheKey)
}
const diskCachePath = getDiskCachePath(prefix, feedUrl)
if (diskCachePath) {
try {
const payload = JSON.parse(fs.readFileSync(diskCachePath, 'utf-8'))
if (process.env.NODE_ENV === 'development')
console.log(`Changelog disk-cache HIT on ${diskCachePath}`)
// Also, for next time, within this Node process, put it into
// the global cache so we don't need to read from disk again.
globalCache.set(cacheKey, payload)
return payload
} catch (err) {
// If it wasn't on disk, that's fine.
if (err.code === 'ENOENT') return
// The JSON.parse() most likely failed. Ignore the error
// but delete the file so it won't be attempted again.
if (err instanceof SyntaxError) {
fs.unlinkSync(diskCachePath)
return
}
throw err
}
}
}
function setChangelogItemsCache(prefix, feedUrl, payload) {
const cacheKey = getChangelogCacheKey(prefix, feedUrl)
globalCache.set(cacheKey, payload)
const diskCachePath = getDiskCachePath(prefix, feedUrl)
// Note that `diskCachePath` is falsy if NODE_ENV==production which
// means we're not writing to disk in production.
if (diskCachePath) {
fs.writeFileSync(diskCachePath, JSON.stringify(payload), 'utf-8')
if (process.env.NODE_ENV === 'development')
console.log(`Wrote changelog cache to disk ${diskCachePath}`)
}
}