blob: 90621a45659d2a4b573b8c45ead2f9a9ab2878a2 [file] [edit]
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */
/** @import {BrowserName, BrowserStatus, CompatData} from '../../types/types.js' */
/**
* @typedef {object} RSSItem
* @property {string} title
* @property {string} pubDate
* @property {string} description
* @property {string} link
*/
import { styleText } from 'node:util';
import xml2js from 'xml2js';
const USER_AGENT =
'MDN-Browser-Release-Update-Bot/1.0 (+https://developer.mozilla.org/)';
/**
* newBrowserEntry - Add a new browser entry in the JSON list
* @param {*} json json file to update
* @param {string} browser the entry name where to add it in the bcd file
* @param {string} version new version to add
* @param {string} status new status
* @param {string | undefined} engine name of the engine
* @param {string | undefined} releaseDate new release date
* @param {string | undefined} releaseNotesURL url of the release notes
* @param {string | undefined} engineVersion the version of the engine
* @returns {string} Text describing what has been added
*/
export const newBrowserEntry = (
json,
browser,
version,
status,
engine,
releaseDate,
releaseNotesURL,
engineVersion,
) => {
const release = (json.browsers[browser].releases[version] = {});
if (releaseDate) {
release['release_date'] = releaseDate;
}
if (releaseNotesURL) {
release['release_notes'] = releaseNotesURL;
}
release['status'] = status;
release['engine'] = engine;
if (engineVersion) {
release['engine_version'] = engineVersion.toString();
}
return styleText(
'yellow',
`\n- New release detected for ${styleText('bold', browser)}: Version ${styleText('bold', version)} as a ${styleText('bold', status)} release.`,
);
};
/**
* updateBrowserEntry - Update browser entry in the JSON list
* @param {*} json json file to update
* @param {string} browser the entry name where to add it in the bcd file
* @param {string} version new version to add
* @param {string | undefined} releaseDate new release date
* @param {string} status new status
* @param {string | undefined} releaseNotesURL url of the release notes
* @param {string | undefined} engineVersion the version of the engine
* @returns {string} Text describing what has been updated
*/
export const updateBrowserEntry = (
json,
browser,
version,
releaseDate,
status,
releaseNotesURL,
engineVersion,
) => {
const entry = json.browsers[browser].releases[version];
let result = '';
if (entry['status'] !== status) {
result += styleText(
'cyan',
`\n- New status for ${styleText('bold', `${browser} ${version}`)}: ${styleText('bold', status)}, previously ${entry['status']}.`,
);
entry['status'] = status;
}
if (releaseDate && entry['release_date'] !== releaseDate) {
result += styleText(
'cyan',
`\n- New release date for ${styleText('bold', `${browser} ${version}`)}: ${styleText('bold', releaseDate)}, previously ${entry['release_date']}.`,
);
entry['release_date'] = releaseDate;
}
if (releaseNotesURL && entry['release_notes'] !== releaseNotesURL) {
result += styleText(
'cyan',
`\n- New release notes for ${styleText('bold', `${browser} ${version}`)}: ${styleText('bold', releaseNotesURL)}, previously ${entry['release_notes']}.`,
);
entry['release_notes'] = releaseNotesURL;
}
if (engineVersion && entry['engine_version'] != engineVersion) {
result += styleText(
'cyan',
`\n- New engine version for ${styleText('bold', `${browser} ${version}`)}: ${styleText('bold', String(engineVersion))}, previously ${entry['engine_version']}.`,
);
entry['engine_version'] = engineVersion.toString();
}
return result;
};
/**
* Creates or updates a browser entry, depending on whether it already exists.
* @param {*} json json file to update
* @param {string} browser the entry name where to add it in the bcd file
* @param {string} version the version to add
* @param {'retired' | 'current' | 'beta' | 'nightly'} status the status
* @param {string | undefined} engine the name of the engine
* @param {string | undefined} engineVersion the version of the engine
* @param {string | undefined} [releaseDate] the release date
* @param {string | undefined} [releaseNotesURL] url of the release notes
* @returns {string} Text describing what has been updated
*/
export const createOrUpdateBrowserEntry = (
json,
browser,
version,
status,
engine,
engineVersion,
releaseDate = undefined,
releaseNotesURL = undefined,
) => {
if (json.browsers[browser].releases[version]) {
return updateBrowserEntry(
json,
browser,
version,
releaseDate,
status,
releaseNotesURL,
engineVersion,
);
}
return newBrowserEntry(
json,
browser,
version,
status,
engine,
releaseDate,
releaseNotesURL,
engineVersion,
);
};
/**
* Updates the status of a browser release.
* @param {CompatData} json json file to update
* @param {BrowserName} browser the entry name where to add it in the bcd file
* @param {string} version the version to add
* @param {BrowserStatus} status the status
* @returns {string} Text describing what has been updated
*/
export const setBrowserReleaseStatus = (json, browser, version, status) => {
const release = json.browsers[browser].releases[version];
if (release.status === status) {
return '';
}
return updateBrowserEntry(
json,
browser,
version,
release.release_date,
status,
'',
'',
);
};
/**
* Fetches an RSS feed, using a typical RSS user agent.
* @param {string} url The URL of the RSS feed.
* @returns {Promise<string>} Promise
*/
const fetchRSS = async (url) => {
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 {string} rssText The content of the RSS feed.
* @returns {Promise<RSSItem[]>} the RSS items.
*/
const parseRSS = async (rssText) => {
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 {string} url The URL of the RSS feed.
* @returns {Promise<RSSItem[]>} the RSS items.
*/
export const getRSSItems = async (url) => {
const rssText = await fetchRSS(url);
const items = await parseRSS(rssText);
return Array.isArray(items) ? items : [items];
};
/**
* Converts a message into a GFM noteblock.
* @param {'NOTE' | 'WARNING' | 'CAUTION'} type the type of the noteblock.
* @param {string} message the message of the noteblock.
* @returns {string} the message as a GFM noteblock.
*/
export const gfmNoteblock = (type, message) =>
`> [!${type}]\n${message
.split('\n')
.map((line) => `> ${line}`)
.join('\n')}`;