Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/build/pr-check-cache-files.ts
13383 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { exec } from 'child_process';
7
import { promisify } from 'util';
8
9
const execAsync = promisify(exec);
10
11
type Commit = {
12
readonly sha: string;
13
readonly committer: {
14
readonly login: string;
15
};
16
readonly commit: {
17
readonly verification: {
18
readonly verified: boolean;
19
readonly reason: string;
20
};
21
};
22
readonly files: readonly PullRequestFile[];
23
}
24
25
type PullRequestFile = {
26
readonly filename: string;
27
readonly status: string;
28
}
29
30
type PullRequestCommit = {
31
readonly sha: string;
32
}
33
34
const collaborators = [
35
"aeschli", "aiday-mar", "alexdima", "alexr00", "amunger", "anthonykim1", "bamurtaugh", "benibenj", "benvillalobos", "bhavyaus",
36
"binderjoe", "bpasero", "bryanchen-d", "burkeholland", "chrmarti", "connor4312", "cwebster-99", "dbaeumer", "deepak1556",
37
"devinvalenciano", "digitarald", "dileepyavan", "dineshc-msft", "dmitrivMS", "DonJayamanne", "egamma", "eleanorjboyd", "eli-w-king",
38
"hawkticehurst", "hediet", "isidorn", "jo-oikawa", "joaomoreno", "joshspicer", "jrieken", "jruales", "justschen", "karthiknadig",
39
"kieferrm", "kkbrooks", "kycutler", "lramos15", "lszomoru", "luabud", "meganrogge", "minsa110", "mjbvz", "mrleemurray", "nguyenchristy",
40
"ntrogh", "olguzzar", "osortega", "pierceboggan", "pwang347", "rebornix", "roblourens", "rzhao271", "sandy081", "sbatten", "TylerLeonhardt",
41
"Tyriar", "ulugbekna", "vijayupadya", "Yoyokrazy"
42
];
43
44
// TODO@lszomoru - Investigate issues with the `/collaborators` endpoint
45
// async function getCollaborators(repository: string): Promise<readonly string[]> {
46
// const { stdout, stderr } = await execAsync(
47
// `gh api -H "Accept: application/vnd.github+json" /repos/${repository}/collaborators --paginate`, { maxBuffer: 25 * 1024 * 1024 });
48
49
// if (stderr) {
50
// throw new Error(`Error fetching repository collaborators - ${stderr}`);
51
// }
52
53
// return JSON.parse(stdout) as ReadonlyArray<string>;
54
// }
55
56
async function getCommit(repository: string, sha: string): Promise<Commit> {
57
const { stdout, stderr } = await execAsync(
58
`gh api -H "Accept: application/vnd.github+json" /repos/${repository}/commits/${sha}`, { maxBuffer: 25 * 1024 * 1024 });
59
60
if (stderr) {
61
throw new Error(`Error fetching commit ${sha} - ${stderr}`);
62
}
63
64
return JSON.parse(stdout) as Commit;
65
}
66
67
async function getPullRequestFiles(repository: string, pullRequestNumber: string): Promise<readonly PullRequestFile[]> {
68
const { stdout, stderr } = await execAsync(
69
`gh api -H "Accept: application/vnd.github+json" /repos/${repository}/pulls/${pullRequestNumber}/files --paginate`, { maxBuffer: 25 * 1024 * 1024 });
70
71
if (stderr) {
72
throw new Error(`Error fetching pull request files - ${stderr}`);
73
}
74
75
return JSON.parse(stdout) as readonly PullRequestFile[];
76
}
77
78
async function getPullRequestCommits(repository: string, pullRequestNumber: string): Promise<readonly string[]> {
79
const { stdout, stderr } = await execAsync(
80
`gh api -H "Accept: application/vnd.github+json" /repos/${repository}/pulls/${pullRequestNumber}/commits --paginate`, { maxBuffer: 25 * 1024 * 1024 });
81
82
if (stderr) {
83
throw new Error(`Error fetching pull request commits - ${stderr}`);
84
}
85
86
return JSON.parse(stdout).map((commit: PullRequestCommit) => commit.sha);
87
}
88
89
async function checkDatabaseFile(files: ReadonlyArray<PullRequestFile>): Promise<boolean> {
90
const baseFile = files.find(f => f.filename.toLowerCase() === 'test/simulation/cache/base.sqlite');
91
if (!baseFile) {
92
console.log('✅ Pull request does not contain the base file.');
93
return true;
94
}
95
96
const statusCheck = baseFile.status === 'modified';
97
console.log(`🔍 Pull request contains the base file. Checking status...`);
98
console.log(` - 🗄️ ${baseFile.filename}; Status: ${baseFile.status} ${statusCheck ? '✅' : '⛔'}`);
99
100
return statusCheck;
101
}
102
103
async function checkDatabaseLayerFiles(repository: string, pullRequestNumber: string, files: readonly PullRequestFile[])
104
: Promise<{ statusCheck: boolean; verifiedCheck: boolean; collaboratorCheck: boolean }> {
105
const layerFiles = files.filter(f => f.filename.toLowerCase().startsWith('test/simulation/cache/layers/'));
106
107
if (layerFiles.length === 0) {
108
console.log('✅ Pull request does not contain any layer files.');
109
return { statusCheck: true, verifiedCheck: true, collaboratorCheck: true };
110
}
111
112
// Get collaborators and commits for the pull request
113
// const collaborators = await getCollaborators(repository);
114
const pullRequestCommits = await getPullRequestCommits(repository, pullRequestNumber);
115
const commitsWithDetails = await Promise.all(pullRequestCommits.map(sha => getCommit(repository, sha)));
116
117
let statusCheckResult = true, verifiedCheckResult = true, collaboratorCheckResult = true;
118
console.log(`🔍 Pull request contains ${layerFiles.length} layer files. Checking status and author...`);
119
120
for (const file of layerFiles) {
121
const statusCheck = file.status === 'added' || file.status === 'removed';
122
console.log(` - 🗄️ ${file.filename}`);
123
console.log(` - Status: ${file.status} ${statusCheck ? '✅' : '⛔'}`);
124
125
if (!statusCheck) {
126
statusCheckResult = false;
127
}
128
129
// List of commits that contain the file
130
const commits = commitsWithDetails.filter(c =>
131
c.files.some(f => f.filename === file.filename));
132
133
console.log(` - Commit(s):`);
134
for (const commit of commits) {
135
const collaboratorCheck = collaborators.find(c => c === commit.committer.login);
136
const verifiedCheck = commit.commit.verification.verified && commit.commit.verification.reason === 'valid';
137
console.log(` - ${commit.sha} by ${commit.committer.login}. Collaborator: ${collaboratorCheck ? '✅' : '⛔'} Verified: ${verifiedCheck ? '✅' : '⛔'}`);
138
139
if (!verifiedCheck) {
140
verifiedCheckResult = false;
141
}
142
if (!collaboratorCheck) {
143
collaboratorCheckResult = false;
144
}
145
}
146
}
147
148
return { statusCheck: statusCheckResult, verifiedCheck: verifiedCheckResult, collaboratorCheck: collaboratorCheckResult };
149
}
150
151
async function main() {
152
try {
153
const repository = process.env['REPOSITORY'];
154
const pullRequestNumber = process.env['PULL_REQUEST'];
155
156
if (!repository || !pullRequestNumber) {
157
throw new Error('Missing required environment variables: REPOSITORY or PULL_REQUEST');
158
}
159
160
console.log(`🔍 Checking pull request #${pullRequestNumber} in repository "${repository}"...`);
161
162
// Get a list of files in the pull request
163
const files = await getPullRequestFiles(repository, pullRequestNumber);
164
165
// 1. Check base file status
166
const baseCheckResult = await checkDatabaseFile(files);
167
168
// 2. Check cache layer file(s) status and author
169
const layerCheckResult = await checkDatabaseLayerFiles(repository, pullRequestNumber, files);
170
171
if (!baseCheckResult) {
172
throw new Error('Base file can only be modified in a pull request.');
173
}
174
if (!layerCheckResult.statusCheck) {
175
throw new Error('Cache layer files can only be added or deleted, never modified');
176
}
177
if (!layerCheckResult.verifiedCheck || !layerCheckResult.collaboratorCheck) {
178
throw new Error('Cache layer files can only be added by VS Code team members with signed commits');
179
}
180
} catch (error) {
181
console.log('::error::⛔', error);
182
process.exit(1);
183
}
184
}
185
186
if (require.main === module) {
187
main();
188
}
189
190