main
js 190 lines 4.85 KB
Raw
1 'use strict';
2
3 const https = require('https');
4 const path = require('path');
5
6 const {execFileAsync, repoRoot} = require('./utils');
7
8 async function fetchNpmInfo(packageName, {log}) {
9 const npmArgs = ['view', `${packageName}@latest`, '--json'];
10 const options = {cwd: repoRoot, maxBuffer: 10 * 1024 * 1024};
11 log(`Fetching npm info for ${packageName}...`);
12 const {stdout} = await execFileAsync('npm', npmArgs, options);
13
14 let data = stdout.trim();
15 if (!data) {
16 throw new Error(`npm view returned empty result for ${packageName}`);
17 }
18
19 let info = JSON.parse(data);
20 if (Array.isArray(info)) {
21 info = info[info.length - 1];
22 }
23
24 const version = info.version || info['dist-tags']?.latest;
25 let gitHead = info.gitHead || null;
26
27 if (!gitHead) {
28 const gitHeadResult = await execFileAsync(
29 'npm',
30 ['view', `${packageName}@${version}`, 'gitHead'],
31 {cwd: repoRoot, maxBuffer: 1024 * 1024}
32 );
33 const possibleGitHead = gitHeadResult.stdout.trim();
34 if (
35 possibleGitHead &&
36 possibleGitHead !== 'undefined' &&
37 possibleGitHead !== 'null'
38 ) {
39 log(`Found gitHead for ${packageName}@${version}: ${possibleGitHead}`);
40 gitHead = possibleGitHead;
41 }
42 }
43
44 if (!version) {
45 throw new Error(
46 `Unable to determine latest published version for ${packageName}`
47 );
48 }
49 if (!gitHead) {
50 throw new Error(
51 `Unable to determine git commit for ${packageName}@${version}`
52 );
53 }
54
55 return {
56 publishedVersion: version,
57 gitHead,
58 };
59 }
60
61 async function collectCommitsSince(packageName, sinceGitSha, {log}) {
62 log(`Collecting commits for ${packageName} since ${sinceGitSha}...`);
63 await execFileAsync('git', ['cat-file', '-e', `${sinceGitSha}^{commit}`], {
64 cwd: repoRoot,
65 });
66 const {stdout} = await execFileAsync(
67 'git',
68 [
69 'rev-list',
70 '--reverse',
71 `${sinceGitSha}..HEAD`,
72 '--',
73 path.posix.join('packages', packageName),
74 ],
75 {cwd: repoRoot, maxBuffer: 10 * 1024 * 1024}
76 );
77
78 return stdout
79 .trim()
80 .split('\n')
81 .map(line => line.trim())
82 .filter(Boolean);
83 }
84
85 async function loadCommitDetails(sha, {log}) {
86 log(`Loading commit details for ${sha}...`);
87 const format = ['%H', '%s', '%an', '%ae', '%ct', '%B'].join('%n');
88 const {stdout} = await execFileAsync(
89 'git',
90 ['show', '--quiet', `--format=${format}`, sha],
91 {cwd: repoRoot, maxBuffer: 10 * 1024 * 1024}
92 );
93
94 const [commitSha, subject, authorName, authorEmail, timestamp, ...rest] =
95 stdout.split('\n');
96 const body = rest.join('\n').trim();
97
98 return {
99 sha: commitSha.trim(),
100 subject: subject.trim(),
101 authorName: authorName.trim(),
102 authorEmail: authorEmail.trim(),
103 timestamp: +timestamp.trim() || 0,
104 body,
105 };
106 }
107
108 function extractPrNumber(subject, body) {
109 const patterns = [
110 /\(#(\d+)\)/,
111 /https:\/\/github\.com\/facebook\/react\/pull\/(\d+)/,
112 ];
113
114 for (let i = 0; i < patterns.length; i++) {
115 const pattern = patterns[i];
116 const subjectMatch = subject && subject.match(pattern);
117 if (subjectMatch) {
118 return subjectMatch[1];
119 }
120 const bodyMatch = body && body.match(pattern);
121 if (bodyMatch) {
122 return bodyMatch[1];
123 }
124 }
125
126 return null;
127 }
128
129 async function fetchPullRequestMetadata(prNumber, {log}) {
130 log(`Fetching PR metadata for #${prNumber}...`);
131 const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
132 const requestOptions = {
133 hostname: 'api.github.com',
134 path: `/repos/facebook/react/pulls/${prNumber}`,
135 method: 'GET',
136 headers: {
137 'User-Agent': 'generate-changelog-script',
138 Accept: 'application/vnd.github+json',
139 },
140 };
141 if (token) {
142 requestOptions.headers.Authorization = `Bearer ${token}`;
143 }
144
145 return new Promise(resolve => {
146 const req = https.request(requestOptions, res => {
147 let raw = '';
148 res.on('data', chunk => {
149 raw += chunk;
150 });
151 res.on('end', () => {
152 if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
153 try {
154 const json = JSON.parse(raw);
155 resolve({
156 authorLogin: json.user?.login || null,
157 });
158 } catch (error) {
159 process.stderr.write(
160 `Warning: unable to parse GitHub response for PR #${prNumber}: ${error.message}\n`
161 );
162 resolve(null);
163 }
164 } else {
165 process.stderr.write(
166 `Warning: GitHub API request failed for PR #${prNumber} with status ${res.statusCode}\n`
167 );
168 resolve(null);
169 }
170 });
171 });
172
173 req.on('error', error => {
174 process.stderr.write(
175 `Warning: GitHub API request errored for PR #${prNumber}: ${error.message}\n`
176 );
177 resolve(null);
178 });
179
180 req.end();
181 });
182 }
183
184 module.exports = {
185 fetchNpmInfo,
186 collectCommitsSince,
187 loadCommitDetails,
188 extractPrNumber,
189 fetchPullRequestMetadata,
190 };