UNPKG

check-outdated

Version:

Light-weight CLI tool to ensure that your dependencies are up to date, otherwise the process is terminated with status code 1.

246 lines (212 loc) 7.14 kB
/** * @file Generate URLs. */ const https = require('https'); const STATUS_OK = 200; /** @typedef {import('./files').PackageJSON} PackageJSON */ /** * @type {readonly { * readonly regExp: RegExp; * readonly getRepositoryURL: (match: readonly string[], directory: string) => string; * readonly getChangelogURL: (match: readonly string[], directory: string) => Promise<string> | string; * }[]} */ const REPOSITORY_URLS = [ { regExp: /^github:(.+)/u, getRepositoryURL: (match, directory) => `https://github.com/${match[1]}${directory}`, getChangelogURL: (match) => `https://github.com/${match[1]}/releases` }, { regExp: /^gist:(.+)/u, getRepositoryURL: (match, directory) => `https://gist.github.com/${match[1]}${directory}`, getChangelogURL: (match) => `https://gist.github.com/${match[1]}/revisions` }, { regExp: /^bitbucket:(.+)/u, getRepositoryURL: (match, directory) => `https://bitbucket.org/${match[1]}${directory}`, // @todo Right now, I can't find a reference, to a public bitbucket repository getChangelogURL: (match) => `https://bitbucket.org/${match[1]}` }, { regExp: /^gitlab:(.+)/u, getRepositoryURL: (match) => `https://gitlab.com/${match[1]}`, getChangelogURL: (match) => `https://gitlab.com/${match[1]}/-/releases` }, { regExp: /^git\+(https?:\/\/.+)\.git$/u, getRepositoryURL: (match, directory) => `${match[1]}${directory}`, getChangelogURL: async (match, directory) => getChangelogFromURL(match[1], directory) }, { regExp: /^git\+ssh:\/\/git@(.+)\.git$/u, getRepositoryURL: (match, directory) => `https://${match[1]}${directory}`, getChangelogURL: async (match, directory) => getChangelogFromURL(`https://${match[1]}`, directory) }, // Fallback (should be the last item) { regExp: /^(https?:\/\/.+?)(\.git)?$/u, getRepositoryURL: (match, directory) => `${match[1]}${directory}`, getChangelogURL: async (match, directory) => getChangelogFromURL(match[1], directory) } ]; /** * Generates a link to the npmjs.com website, based on a dependency name.. * * @public * @param {string} packageName - Options which shall be appended to the `npm outdated` command-line call. * @returns {string} The link to the package on the npmjs.com website. */ function getNpmJSLink (packageName) { return `https://www.npmjs.com/package/${encodeURIComponent(packageName)}`; } /** * Returns the URL of the author from the package.json. * * @public * @param {PackageJSON} packageJSON - The content of a package.json. * @returns {string | undefined} The URL of the author. */ function getPackageAuthor (packageJSON) { if (packageJSON.author) { if (typeof packageJSON.author === 'string') { const match = (/\((http.+?)\)/u).exec(packageJSON.author); if (match !== null) { return match[1]; } } else if (packageJSON.author.url) { return packageJSON.author.url; } } return undefined; } /** * Returns the URL of the homepage from the package.json. * * @public * @param {PackageJSON} packageJSON - The content of a package.json. * @returns {string | undefined} The URL of the homepage. */ function getPackageHomepage (packageJSON) { if (packageJSON.homepage) { return packageJSON.homepage; } return undefined; } /** * Returns the URL of the repository from the package.json. * * @public * @param {PackageJSON} packageJSON - The content of a package.json. * @param {boolean} [linkToChangelog] - If the returned URL should link to the "Releases" page. * @returns {Promise<string | undefined>} The URL of the repository. */ async function getPackageRepository (packageJSON, linkToChangelog) { if (packageJSON.repository) { if (typeof packageJSON.repository === 'string') { for (const repo of REPOSITORY_URLS) { const match = repo.regExp.exec(packageJSON.repository); if (match !== null) { if (linkToChangelog) { return repo.getChangelogURL(match, ''); } return repo.getRepositoryURL(match, ''); } } } else if (packageJSON.repository.url) { for (const repo of REPOSITORY_URLS) { const match = repo.regExp.exec(packageJSON.repository.url); if (match !== null) { const directory = (packageJSON.repository.directory ? packageJSON.repository.directory.replace(/^\/*/u, '/').replace(/\/+$/u, '') : ''); if (linkToChangelog) { return repo.getChangelogURL(match, directory); } return repo.getRepositoryURL(match, directory); } } } } return undefined; } /** * Tries to determine the URL to the changelog for a specific service `url`. * * @private * @param {string} url - Service URL * @param {string} directory - Sub-directory path, starting with "/" or an empty string * @returns {Promise<string>} Either returns an URL to the changelog for a specific service, or it returns `url`. */ async function getChangelogFromURL (url, directory) { const githubMatch = (/^https?:\/\/github.com\/([^/]+?\/[^/#?]+)/u).exec(url); if (githubMatch !== null) { const linkToChangelog = await getFileOnGitHub(githubMatch[1], 'CHANGELOG.md', directory, 256); if (linkToChangelog) { return linkToChangelog; } return `https://github.com/${githubMatch[1]}/releases`; } const gistMatch = (/^https?:\/\/gist.github.com\/([^/]+?\/)?[^/#?]+/u).exec(url); if (gistMatch !== null) { return `${gistMatch[0]}/revisions`; } const gitlabMatch = (/^https?:\/\/gitlab.com\/[^/]+?\/[^/#?]+/u).exec(url); if (gitlabMatch !== null) { return `${gitlabMatch[0]}/-/releases`; } return url; } /** * Returns a link to the HTML representation of a specific file in a GitHub repository. * * @private * @param {string} repoName - GitHub repository name, e.g. "jens.duttke/check-outdated" * @param {string} fileName - File name, e.g. "README.md" * @param {string} directory - Sub-directory path, starting with "/" or an empty string * @param {number} minimumContentSize - The minimum required size of the file content. * @returns {Promise<string | undefined>} Returns either the URL to the HTML representation of the file, or `undefined` if the file has not been found, or if it is too small. */ async function getFileOnGitHub (repoName, fileName, directory = '', minimumContentSize = 0) { return new Promise((resolve) => { https.get({ host: 'api.github.com', path: `/repos/${repoName}/contents${directory}/${fileName}`, method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0' } }, (response) => { if (response.statusCode !== STATUS_OK) { response.destroy(); resolve(undefined); return; } /** @type {string[]} */ const data = []; response.setEncoding('utf8'); response.on('data', (chunk) => data.push(chunk)); response.on('end', () => { try { const json = JSON.parse(data.join('')); if (json.html_url && json.size >= minimumContentSize) { resolve(json.html_url); } else { resolve(undefined); } } catch { resolve(undefined); } }); response.on('error', () => resolve(undefined)); }); }); } module.exports = { getNpmJSLink, getPackageAuthor, getPackageHomepage, getPackageRepository };