-
Notifications
You must be signed in to change notification settings - Fork 226
Expand file tree
/
Copy pathrepository-selection.ts
More file actions
210 lines (179 loc) · 6.61 KB
/
repository-selection.ts
File metadata and controls
210 lines (179 loc) · 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import * as fs from 'fs-extra';
import { QuickPickItem, window } from 'vscode';
import { logger } from '../logging';
import { getRemoteRepositoryLists, getRemoteRepositoryListsPath } from '../config';
import { OWNER_REGEX, REPO_REGEX } from '../pure/helpers-pure';
import { UserCancellationException } from '../commandRunner';
export interface RepositorySelection {
repositories?: string[];
repositoryLists?: string[];
owners?: string[];
}
interface RepoListQuickPickItem extends QuickPickItem {
repositories?: string[];
repositoryList?: string;
useCustomRepo?: boolean;
useAllReposOfOwner?: boolean;
}
interface RepoList {
label: string;
repositories: string[];
}
/**
* Gets the repositories or repository lists to run the query against.
* @returns The user selection.
*/
export async function getRepositorySelection(): Promise<RepositorySelection> {
const quickPickItems = [
createCustomRepoQuickPickItem(),
createAllReposOfOwnerQuickPickItem(),
...createSystemDefinedRepoListsQuickPickItems(),
...(await createUserDefinedRepoListsQuickPickItems()),
];
const options = {
placeHolder: 'Select a repository list. You can define repository lists in the `codeQL.variantAnalysis.repositoryLists` setting.',
ignoreFocusOut: true,
};
const quickpick = await window.showQuickPick<RepoListQuickPickItem>(
quickPickItems,
options);
if (quickpick?.repositories?.length) {
void logger.log(`Selected repositories: ${quickpick.repositories.join(', ')}`);
return { repositories: quickpick.repositories };
} else if (quickpick?.repositoryList) {
void logger.log(`Selected repository list: ${quickpick.repositoryList}`);
return { repositoryLists: [quickpick.repositoryList] };
} else if (quickpick?.useCustomRepo) {
const customRepo = await getCustomRepo();
if (customRepo === undefined) {
// The user cancelled, do nothing.
throw new UserCancellationException('No repositories selected', true);
}
if (!customRepo || !REPO_REGEX.test(customRepo)) {
throw new UserCancellationException('Invalid repository format. Please enter a valid repository in the format <owner>/<repo> (e.g. github/codeql)');
}
void logger.log(`Entered repository: ${customRepo}`);
return { repositories: [customRepo] };
} else if (quickpick?.useAllReposOfOwner) {
const owner = await getOwner();
if (owner === undefined) {
// The user cancelled, do nothing.
throw new UserCancellationException('No repositories selected', true);
}
if (!owner || !OWNER_REGEX.test(owner)) {
throw new Error(`Invalid user or organization: ${owner}`);
}
void logger.log(`Entered owner: ${owner}`);
return { owners: [owner] };
} else {
// We don't need to display a warning pop-up in this case, since the user just escaped out of the operation.
// We set 'true' to make this a silent exception.
throw new UserCancellationException('No repositories selected', true);
}
}
/**
* Checks if the selection is valid or not.
* @param repoSelection The selection to check.
* @returns A boolean flag indicating if the selection is valid or not.
*/
export function isValidSelection(repoSelection: RepositorySelection): boolean {
const repositories = repoSelection.repositories || [];
const repositoryLists = repoSelection.repositoryLists || [];
const owners = repoSelection.owners || [];
return (repositories.length > 0 || repositoryLists.length > 0 || owners.length > 0);
}
function createSystemDefinedRepoListsQuickPickItems(): RepoListQuickPickItem[] {
const topNs = [10, 100, 1000];
return topNs.map(n => ({
label: '$(star) Top ' + n,
repositoryList: `top_${n}`,
alwaysShow: true
} as RepoListQuickPickItem));
}
async function readExternalRepoLists(): Promise<RepoList[]> {
const repoLists: RepoList[] = [];
const path = getRemoteRepositoryListsPath();
if (!path) {
return repoLists;
}
await validateExternalRepoListsFile(path);
const json = await readExternalRepoListsJson(path);
for (const [repoListName, repositories] of Object.entries(json)) {
if (!Array.isArray(repositories)) {
throw Error('Invalid repository lists file. It should contain an array of repositories for each list.');
}
repoLists.push({
label: repoListName,
repositories
});
}
return repoLists;
}
async function validateExternalRepoListsFile(path: string): Promise<void> {
const pathExists = await fs.pathExists(path);
if (!pathExists) {
throw Error(`External repository lists file does not exist at ${path}`);
}
const pathStat = await fs.stat(path);
if (pathStat.isDirectory()) {
throw Error('External repository lists path should not point to a directory');
}
}
async function readExternalRepoListsJson(path: string): Promise<Record<string, unknown>> {
let json;
try {
const fileContents = await fs.readFile(path, 'utf8');
json = await JSON.parse(fileContents);
} catch (error) {
throw Error('Invalid repository lists file. It should contain valid JSON.');
}
if (Array.isArray(json)) {
throw Error('Invalid repository lists file. It should be an object mapping names to a list of repositories.');
}
return json;
}
function readRepoListsFromSettings(): RepoList[] {
const repoLists = getRemoteRepositoryLists();
if (!repoLists) {
return [];
}
return Object.entries(repoLists).map<RepoList>(([label, repositories]) => (
{
label,
repositories
}
));
}
async function createUserDefinedRepoListsQuickPickItems(): Promise<RepoListQuickPickItem[]> {
const repoListsFromSetings = readRepoListsFromSettings();
const repoListsFromExternalFile = await readExternalRepoLists();
return [...repoListsFromSetings, ...repoListsFromExternalFile];
}
function createCustomRepoQuickPickItem(): RepoListQuickPickItem {
return {
label: '$(edit) Enter a GitHub repository',
useCustomRepo: true,
alwaysShow: true,
};
}
function createAllReposOfOwnerQuickPickItem(): RepoListQuickPickItem {
return {
label: '$(edit) Enter a GitHub user or organization',
useAllReposOfOwner: true,
alwaysShow: true
};
}
async function getCustomRepo(): Promise<string | undefined> {
return await window.showInputBox({
title: 'Enter a GitHub repository in the format <owner>/<repo> (e.g. github/codeql)',
placeHolder: '<owner>/<repo>',
prompt: 'Tip: you can save frequently used repositories in the `codeQL.variantAnalysis.repositoryLists` setting',
ignoreFocusOut: true,
});
}
async function getOwner(): Promise<string | undefined> {
return await window.showInputBox({
title: 'Enter a GitHub user or organization',
ignoreFocusOut: true
});
}