-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
import fs from 'node:fs/promises'; | ||
|
||
import xml2js from 'xml2js'; | ||
|
||
import stringify from '../lib/stringify-and-order-properties.js'; | ||
|
||
import { createOrUpdateBrowserEntry, updateBrowserEntry } from './utils'; | ||
|
||
const USER_AGENT = | ||
'MDN-Browser-Release-Update-Bot/1.0 (+https://developer.mozilla.org/)'; | ||
|
||
interface RSSItem { | ||
title: string; | ||
pubDate: string; | ||
description: string; | ||
link: string; | ||
} | ||
|
||
interface Release { | ||
version: string; | ||
date: string; | ||
releaseNote: string; | ||
channel: 'current'; | ||
engine: 'Blink'; | ||
engineVersion: string; | ||
} | ||
|
||
/** | ||
* Fetches an RSS feed, using a typical RSS user agent. | ||
* @param url The URL of the RSS feed. | ||
* @returns Promise | ||
*/ | ||
const fetchRSS = async (url: string) => { | ||
const response = await fetch(url, { | ||
headers: { | ||
'User-Agent': USER_AGENT, | ||
}, | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error(`HTTP ${response.status}`); | ||
} | ||
|
||
return await response.text(); | ||
}; | ||
|
||
/** | ||
* Parses an RSS feed into a JSON object. | ||
* @param rssText The content of the RSS feed. | ||
* @returns the RSS items. | ||
*/ | ||
const parseRSS = async (rssText: string): Promise<RSSItem[]> => { | ||
const parser = new xml2js.Parser({ explicitArray: false }); | ||
|
||
const result = await parser.parseStringPromise(rssText); | ||
|
||
return result.rss.channel.item; | ||
}; | ||
|
||
/** | ||
* Fetches and parses an RSS feed. | ||
* @param url The URL of the RSS feed. | ||
* @returns the RSS items. | ||
*/ | ||
const getRSSItems = async (url): Promise<RSSItem[]> => { | ||
const rssText = await fetchRSS(url); | ||
const items = await parseRSS(rssText); | ||
|
||
return Array.isArray(items) ? items : [items]; | ||
}; | ||
|
||
/** | ||
* Extracts the latest release from the items. | ||
* @param items | ||
* @param versionPattern | ||
* @param engineVersionPattern | ||
* @returns the latest release, if found, otherwise null. | ||
*/ | ||
const findRelease = ( | ||
items: RSSItem[], | ||
versionPattern: RegExp, | ||
engineVersionPattern: RegExp, | ||
): Release | null => { | ||
const item = items.find( | ||
(item) => | ||
versionPattern.test(item.title) && | ||
engineVersionPattern.test(item.description), | ||
); | ||
|
||
if (!item) { | ||
return null; | ||
} | ||
|
||
const version = (item.title.match(versionPattern) as RegExpMatchArray)[1]; | ||
const date = new Date(item.pubDate).toISOString().split('T')[0]; | ||
const releaseNote = item.link; | ||
const engineVersion = ( | ||
item.description.match(engineVersionPattern) as RegExpMatchArray | ||
)[1]; | ||
|
||
return { | ||
version, | ||
date, | ||
releaseNote, | ||
channel: 'current', | ||
engine: 'Blink', | ||
engineVersion, | ||
}; | ||
}; | ||
|
||
/** | ||
* Converts a message into a GFM noteblock. | ||
* @param type the type of the noteblock. | ||
* @param message the message of the noteblock. | ||
* @returns the message as a GFM noteblock. | ||
*/ | ||
const gfmNoteblock = (type: 'INFO' | 'WARN', message: string) => | ||
`> [!${type}]\n${message | ||
.split('\n') | ||
.map((line) => `> ${line}`) | ||
.join('\n')}`; | ||
|
||
/** | ||
* Updates the JSON files listing the Opera browser releases. | ||
* @param options The list of options for this type of Safari. | ||
* @returns The log of what has been generated (empty if nothing) | ||
*/ | ||
export const updateOperaReleases = async (options) => { | ||
const browser = options.bcdBrowserName; | ||
|
||
const isDesktop = browser === 'opera'; | ||
|
||
let result = ''; | ||
|
||
const items = await getRSSItems(options.releaseFeedURL); | ||
|
||
const release = findRelease( | ||
items.filter((item) => | ||
options.releaseFilterCreator?.includes(item['dc:creator'] ?? true), | ||
), | ||
options.titleVersionPattern, | ||
options.descriptionEngineVersionPattern, | ||
); | ||
|
||
if (!release) { | ||
return gfmNoteblock( | ||
'INFO', | ||
`**${options.browserName}**: No release announcement found among ${items.length} items in [this RSS feed](<${options.releaseFeedURL}>).`, | ||
); | ||
} | ||
|
||
const file = await fs.readFile(`${options.bcdFile}`, 'utf-8'); | ||
const data = JSON.parse(file.toString()); | ||
|
||
const current = structuredClone( | ||
data.browsers[browser].releases[release.version], | ||
); | ||
|
||
if (isDesktop && !current) { | ||
return gfmNoteblock( | ||
'WARN', | ||
`Latest stable **${options.browserName}** release **${release.version}** not yet tracked.`, | ||
); | ||
} | ||
|
||
result += createOrUpdateBrowserEntry( | ||
data, | ||
browser, | ||
release.version, | ||
release.channel, | ||
release.engine, | ||
release.engineVersion, | ||
release.date, | ||
release.releaseNote, | ||
); | ||
|
||
// Set previous release to "retired". | ||
const previousVersion = String(Number(release.version) - 1); | ||
result += updateBrowserEntry( | ||
data, | ||
browser, | ||
previousVersion, | ||
undefined, | ||
'retired', | ||
undefined, | ||
undefined, | ||
); | ||
|
||
if (isDesktop) { | ||
// 1. Set next release to "beta". | ||
result += createOrUpdateBrowserEntry( | ||
data, | ||
browser, | ||
String(Number(release.version) + 1), | ||
'beta', | ||
release.engine, | ||
String(Number(release.engineVersion) + 1), | ||
); | ||
|
||
// 2. Add another release as "nightly". | ||
result += createOrUpdateBrowserEntry( | ||
data, | ||
browser, | ||
String(Number(release.version) + 2), | ||
'nightly', | ||
release.engine, | ||
String(Number(release.engineVersion) + 2), | ||
); | ||
} | ||
|
||
await fs.writeFile(`./${options.bcdFile}`, stringify(data) + '\n'); | ||
|
||
// Returns the log | ||
if (result) { | ||
result = `### Updates for ${options.browserName}${result}`; | ||
} | ||
|
||
return result; | ||
}; |
Oops, something went wrong.