'use strict'; const {stablePackages} = require('../../../ReactVersions'); const {parseArgs} = require('./args'); const { fetchNpmInfo, collectCommitsSince, loadCommitDetails, extractPrNumber, fetchPullRequestMetadata, } = require('./data'); const {summarizePackages} = require('./summaries'); const {buildChangelogEntries, renderChangelog} = require('./formatters'); const {noopLogger} = require('./utils'); async function main() { const {packageSpecs, summarizer, debug, format} = parseArgs( process.argv.slice(2) ); const log = debug ? (...args) => console.log('[generate-changelog]', ...args) : noopLogger; const allStablePackages = Object.keys(stablePackages); const packageTargets = new Map(); for (let i = 0; i < packageSpecs.length; i++) { const spec = packageSpecs[i]; if (!allStablePackages.includes(spec.name)) { throw new Error( `Package "${spec.name}" is not listed in stablePackages.` ); } if (packageTargets.has(spec.name)) { throw new Error(`Package "${spec.name}" was specified more than once.`); } packageTargets.set(spec.name, spec); } const targetPackages = packageSpecs.map(spec => spec.name); log( `Starting changelog generation for: ${packageSpecs .map(spec => `${spec.name}@${spec.displayVersion || spec.version}`) .join(', ')}` ); const packageInfoMap = new Map(); const packageInfoResults = await Promise.all( targetPackages.map(async pkg => { const info = await fetchNpmInfo(pkg, {log}); return {pkg, info}; }) ); for (let i = 0; i < packageInfoResults.length; i++) { const entry = packageInfoResults[i]; packageInfoMap.set(entry.pkg, entry.info); } const commitPackagesMap = new Map(); const commitCollections = await Promise.all( targetPackages.map(async pkg => { const {gitHead} = packageInfoMap.get(pkg); const commits = await collectCommitsSince(pkg, gitHead, {log}); log(`Package ${pkg} has ${commits.length} commit(s) since ${gitHead}.`); return {pkg, commits}; }) ); for (let i = 0; i < commitCollections.length; i++) { const entry = commitCollections[i]; const pkg = entry.pkg; const commits = entry.commits; for (let j = 0; j < commits.length; j++) { const sha = commits[j]; if (!commitPackagesMap.has(sha)) { commitPackagesMap.set(sha, new Set()); } commitPackagesMap.get(sha).add(pkg); } } log(`Found ${commitPackagesMap.size} commits touching target packages.`); if (commitPackagesMap.size === 0) { console.log('No commits found for the selected packages.'); return; } const commitDetails = await Promise.all( Array.from(commitPackagesMap.entries()).map( async ([sha, packagesTouched]) => { const detail = await loadCommitDetails(sha, {log}); detail.packages = packagesTouched; detail.prNumber = extractPrNumber(detail.subject, detail.body); return detail; } ) ); commitDetails.sort((a, b) => a.timestamp - b.timestamp); log(`Ordered ${commitDetails.length} commit(s) chronologically.`); const commitsByPackage = new Map(); commitDetails.forEach(commit => { commit.packages.forEach(pkgName => { if (!commitsByPackage.has(pkgName)) { commitsByPackage.set(pkgName, []); } commitsByPackage.get(pkgName).push(commit); }); }); const uniquePrNumbers = Array.from( new Set(commitDetails.map(commit => commit.prNumber).filter(Boolean)) ); log(`Identified ${uniquePrNumbers.length} unique PR number(s).`); const prMetadata = new Map(); log(`Summarizer selected: ${summarizer || 'none (using commit titles)'}`); const prMetadataResults = await Promise.all( uniquePrNumbers.map(async prNumber => { const meta = await fetchPullRequestMetadata(prNumber, {log}); return {prNumber, meta}; }) ); for (let i = 0; i < prMetadataResults.length; i++) { const entry = prMetadataResults[i]; if (entry.meta) { prMetadata.set(entry.prNumber, entry.meta); } } log(`Fetched metadata for ${prMetadata.size} PR(s).`); const summariesByPackage = await summarizePackages({ summarizer, packageSpecs, packageTargets, commitsByPackage, log, }); const changelogEntries = buildChangelogEntries({ packageSpecs, commitsByPackage, summariesByPackage, prMetadata, }); log('Generated changelog sections.'); const output = renderChangelog(changelogEntries, format); console.log(output); } if (require.main === module) { main().catch(error => { process.stderr.write(`${error.message}\n`); process.exit(1); }); } else { module.exports = main; }