| 1 | 'use strict'; |
| 2 | |
| 3 | const {toCsvRow} = require('./utils'); |
| 4 | |
| 5 | const NO_CHANGES_MESSAGE = 'No changes since the last release.'; |
| 6 | |
| 7 | function buildChangelogEntries({ |
| 8 | packageSpecs, |
| 9 | commitsByPackage, |
| 10 | summariesByPackage, |
| 11 | prMetadata, |
| 12 | }) { |
| 13 | const entries = []; |
| 14 | |
| 15 | for (let i = 0; i < packageSpecs.length; i++) { |
| 16 | const spec = packageSpecs[i]; |
| 17 | const version = spec.displayVersion || spec.version; |
| 18 | const commitsForPackage = commitsByPackage.get(spec.name) || []; |
| 19 | |
| 20 | if (commitsForPackage.length === 0) { |
| 21 | entries.push({ |
| 22 | package: spec.name, |
| 23 | version, |
| 24 | hasChanges: false, |
| 25 | note: NO_CHANGES_MESSAGE, |
| 26 | commits: [], |
| 27 | }); |
| 28 | continue; |
| 29 | } |
| 30 | |
| 31 | const summaryMap = summariesByPackage.get(spec.name) || new Map(); |
| 32 | const commitEntries = commitsForPackage.map(commit => { |
| 33 | let summary = summaryMap.get(commit.sha) || commit.subject; |
| 34 | if (commit.prNumber) { |
| 35 | const prPattern = new RegExp(`\\s*\\(#${commit.prNumber}\\)$`); |
| 36 | summary = summary.replace(prPattern, '').trim(); |
| 37 | } |
| 38 | |
| 39 | const commitSha = commit.sha; |
| 40 | const commitUrl = `https://github.com/facebook/react/commit/${commitSha}`; |
| 41 | const prNumber = commit.prNumber || null; |
| 42 | const prUrl = prNumber |
| 43 | ? `https://github.com/facebook/react/pull/${prNumber}` |
| 44 | : null; |
| 45 | const prEntry = prNumber ? prMetadata.get(prNumber) : null; |
| 46 | |
| 47 | const authorLogin = prEntry?.authorLogin || null; |
| 48 | const authorName = commit.authorName || null; |
| 49 | const authorEmail = commit.authorEmail || null; |
| 50 | |
| 51 | let authorUrl = null; |
| 52 | let authorDisplay = authorName || 'unknown author'; |
| 53 | |
| 54 | if (authorLogin) { |
| 55 | authorUrl = `https://github.com/${authorLogin}`; |
| 56 | authorDisplay = `[@${authorLogin}](${authorUrl})`; |
| 57 | } else if (authorName && authorName.startsWith('@')) { |
| 58 | const username = authorName.slice(1); |
| 59 | authorUrl = `https://github.com/${username}`; |
| 60 | authorDisplay = `[@${username}](${authorUrl})`; |
| 61 | } |
| 62 | |
| 63 | const referenceType = prNumber ? 'pr' : 'commit'; |
| 64 | const referenceId = prNumber ? `#${prNumber}` : commitSha.slice(0, 7); |
| 65 | const referenceUrl = prNumber ? prUrl : commitUrl; |
| 66 | const referenceLabel = prNumber |
| 67 | ? `[#${prNumber}](${prUrl})` |
| 68 | : `commit ${commitSha.slice(0, 7)}`; |
| 69 | |
| 70 | return { |
| 71 | summary, |
| 72 | prNumber, |
| 73 | prUrl, |
| 74 | commitSha, |
| 75 | commitUrl, |
| 76 | author: { |
| 77 | login: authorLogin, |
| 78 | name: authorName, |
| 79 | email: authorEmail, |
| 80 | url: authorUrl, |
| 81 | display: authorDisplay, |
| 82 | }, |
| 83 | reference: { |
| 84 | type: referenceType, |
| 85 | id: referenceId, |
| 86 | url: referenceUrl, |
| 87 | label: referenceLabel, |
| 88 | }, |
| 89 | }; |
| 90 | }); |
| 91 | |
| 92 | entries.push({ |
| 93 | package: spec.name, |
| 94 | version, |
| 95 | hasChanges: true, |
| 96 | note: null, |
| 97 | commits: commitEntries, |
| 98 | }); |
| 99 | } |
| 100 | |
| 101 | return entries; |
| 102 | } |
| 103 | |
| 104 | function renderChangelog(entries, format) { |
| 105 | if (format === 'text') { |
| 106 | const lines = []; |
| 107 | for (let i = 0; i < entries.length; i++) { |
| 108 | const entry = entries[i]; |
| 109 | lines.push(`## ${entry.package}@${entry.version}`); |
| 110 | if (!entry.hasChanges) { |
| 111 | lines.push(`* ${entry.note}`); |
| 112 | lines.push(''); |
| 113 | continue; |
| 114 | } |
| 115 | |
| 116 | entry.commits.forEach(commit => { |
| 117 | lines.push( |
| 118 | `* ${commit.summary} (${commit.reference.label} by ${commit.author.display})` |
| 119 | ); |
| 120 | }); |
| 121 | lines.push(''); |
| 122 | } |
| 123 | |
| 124 | while (lines.length && lines[lines.length - 1] === '') { |
| 125 | lines.pop(); |
| 126 | } |
| 127 | |
| 128 | return lines.join('\n'); |
| 129 | } |
| 130 | |
| 131 | if (format === 'csv') { |
| 132 | const header = [ |
| 133 | 'package', |
| 134 | 'version', |
| 135 | 'summary', |
| 136 | 'reference_type', |
| 137 | 'reference_id', |
| 138 | 'reference_url', |
| 139 | 'author_name', |
| 140 | 'author_login', |
| 141 | 'author_url', |
| 142 | 'author_email', |
| 143 | 'commit_sha', |
| 144 | 'commit_url', |
| 145 | ]; |
| 146 | const rows = [header]; |
| 147 | |
| 148 | entries.forEach(entry => { |
| 149 | if (!entry.hasChanges) { |
| 150 | rows.push([ |
| 151 | entry.package, |
| 152 | entry.version, |
| 153 | entry.note, |
| 154 | '', |
| 155 | '', |
| 156 | '', |
| 157 | '', |
| 158 | '', |
| 159 | '', |
| 160 | '', |
| 161 | '', |
| 162 | '', |
| 163 | ]); |
| 164 | return; |
| 165 | } |
| 166 | |
| 167 | entry.commits.forEach(commit => { |
| 168 | const authorName = |
| 169 | commit.author.name || |
| 170 | (commit.author.login ? `@${commit.author.login}` : 'unknown author'); |
| 171 | rows.push([ |
| 172 | entry.package, |
| 173 | entry.version, |
| 174 | commit.summary, |
| 175 | commit.reference.type, |
| 176 | commit.reference.id, |
| 177 | commit.reference.url, |
| 178 | authorName, |
| 179 | commit.author.login || '', |
| 180 | commit.author.url || '', |
| 181 | commit.author.email || '', |
| 182 | commit.commitSha, |
| 183 | commit.commitUrl, |
| 184 | ]); |
| 185 | }); |
| 186 | }); |
| 187 | |
| 188 | return rows.map(toCsvRow).join('\n'); |
| 189 | } |
| 190 | |
| 191 | if (format === 'json') { |
| 192 | const payload = entries.map(entry => ({ |
| 193 | package: entry.package, |
| 194 | version: entry.version, |
| 195 | hasChanges: entry.hasChanges, |
| 196 | note: entry.hasChanges ? undefined : entry.note, |
| 197 | commits: entry.commits.map(commit => ({ |
| 198 | summary: commit.summary, |
| 199 | prNumber: commit.prNumber, |
| 200 | prUrl: commit.prUrl, |
| 201 | commitSha: commit.commitSha, |
| 202 | commitUrl: commit.commitUrl, |
| 203 | author: { |
| 204 | login: commit.author.login, |
| 205 | name: commit.author.name, |
| 206 | email: commit.author.email, |
| 207 | url: commit.author.url, |
| 208 | display: commit.author.display, |
| 209 | }, |
| 210 | reference: { |
| 211 | type: commit.reference.type, |
| 212 | id: commit.reference.id, |
| 213 | url: commit.reference.url, |
| 214 | label: commit.reference.label, |
| 215 | }, |
| 216 | })), |
| 217 | })); |
| 218 | |
| 219 | return JSON.stringify(payload, null, 2); |
| 220 | } |
| 221 | |
| 222 | throw new Error(`Unsupported format: ${format}`); |
| 223 | } |
| 224 | |
| 225 | module.exports = { |
| 226 | buildChangelogEntries, |
| 227 | renderChangelog, |
| 228 | }; |