main
js 228 lines 5.78 KB
Raw
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 };