docs/lib/changelog.js

131 строка
4.2 KiB
JavaScript

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}`)
}
}