This repository has been archived by the owner on Sep 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
/
generate-config.js
323 lines (299 loc) · 10.1 KB
/
generate-config.js
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
/*
* Helper script to generate config.yml for CodeQL Courses
*
* To regenerate config.yml, run this in a shell:
* node generate-config.js > config.yml
*/
// @ts-check
/**
* Core metadata that is output at the start of config.yml
*/
const META = `
title: <MY-COURSE-TITLE>
tagline: Learn CodeQL in this course
description: >-
Learn CodeQL in this course
template:
repo: <MY-COURSE-REPO>-template
name: <MY-COURSE-REPO>
`;
/**
* File in `responses/` that is used as the content for comments that are posted
* when a user finishes the setup phase.
*
*/
const INTRO_OK_MESSAGE = 'setup-ok.md';
/**
* File in `responses/` that is used as the content for comments that are posted
* when a user finishes a step.
*
* Makes use of the placeholders:
* * `{{next_issue}}` - URL to the issue that details the next set of instructions
* * `{{commit}}` - Sha that was the head of the most recent PUSH
*/
const NEXT_MESSAGE = 'next.md';
/**
* File in `responses/` that is used as the content for comments that are posted
* when a user fails a step.
*
* Makes use of the placeholders:
* * `{{commit}}` - Sha that was the head of the most recent PUSH
*/
const FAIL_MESSAGE = 'fail.md';
/**
* File in `responses/` that is used as the content for the comment that is
* posted when a user finishes the last step.
*
* Makes use of the placeholders:
* * `{{commit}}` - Sha that was the head of the most recent PUSH
*/
const END_MESSAGE = 'end.md';
/**
* Setup steps of this course.
* Each of this step consists in a `.md` file that introduce the
* course, describe the setup ... before the actual query writing begins
*/
const INTRO_ISSUES = [
{
title: 'Welcome!',
instructionsFile: 'step1-welcome.md',
activitiesFiles: []
},
{
title: 'Set up your IDE',
instructionsFile: 'step2-setup.md',
activitiesFiles: ['2-1.md']
}
]
/**
* Each of the steps of this course, each step must have:
*
* * `queryFile` - The file path of the query that the user needs to write for
* this step.
* * `expectedResults` - The number of results this query should produce
* * `instructionsFile` - The file in the `responses/` directory that detail the
* instructions that must be followed for this user to successfully write the
* query.
* * `title` - A title for this step, used in the issue title and
* Learning Lab UI
* * `description` - A description of this step to display in the
* Learning Lab UI
*
* @type Array<{
* queryFile: string;
* expectedResults: number;
* instructionsFile: string;
* title: string;
* description: string;
* activitiesFiles: string[];
* }>
*/
const STEPS = [
{
queryFile: 'step-1.ql',
expectedResults: 3,
instructionsFile: 'step-1.md',
title: 'Your first query',
description: 'Write your first query',
activitiesFiles: []
},
{
queryFile: 'step-2.ql',
expectedResults: 5,
instructionsFile: 'step-2.md',
title: 'Your second query',
description: 'Write your second query',
activitiesFiles: []
}
];
console.log(`
# Generated by generate-config.js
# DO NOT EDIT DIRECTLY
# Instead, edit generate-config.js and re-run script
`.trim() + '\n');
console.log(META.trim());
/**
* @param step {{title: string;}}
* @param i {number}
*/
const issueTitle = (step, i) => `Step ${i + 1 + INTRO_ISSUES.length} - ${step.title}`;
/**
* @param str {string}
*/
const escapeRegExp = (str) =>
str.replace(/[.*+?^${}()|[\]\:\\]/g, '\\$&');
// For some reason, escaping `'` in double-quoted yaml strings
// doesn't work on learning lab, as it throws the exception
// "unknown escape sequence". So we should explicitly only escape double quotes
// and backslashes
const escapeDoubleQuoteYamlString = (str) =>
str.replace(/[\"\\]/g, '\\$&');
console.log(`
before:
# Create Issues for introduction`);
INTRO_ISSUES.map((issue, i) => {
console.log(`
- type: createIssue
title: ${issue.title}
body: ${issue.instructionsFile}
action_id: intro_${i}`);
if (issue.activitiesFiles.length > 0) {
console.log(` comments:`);
issue.activitiesFiles.map(activityFile => {
console.log(` - ${activityFile}`);
})
}
console.log(`
- type: assignRegistrant
issue: '%actions.intro_${i}.data.number%'`)
})
console.log(`
- type: createIssue
title: "${escapeDoubleQuoteYamlString(issueTitle(STEPS[0], 0))}"
body: ${STEPS[0].instructionsFile}
action_id: step_0`);
if (STEPS[0].activitiesFiles.length > 0) {
console.log(` comments:`);
STEPS[0].activitiesFiles.map(activityFile => {
console.log(` - ${activityFile}`);
})
}
console.log(`
- type: assignRegistrant
issue: '%actions.step_0.data.number%'`)
console.log(`
steps:
- title: "Welcome to the course"
description: "Know where to find documentation and help, install CodeQL, setup your IDE."
event: issues.closed
link: '{{ repoUrl }}/issues/1'
actions:
- type: gate
left: '%payload.issue.title%'
operator: search
right: "${escapeDoubleQuoteYamlString(INTRO_ISSUES[INTRO_ISSUES.length - 1].title)}"
- type: respond
issue: "${escapeDoubleQuoteYamlString(INTRO_ISSUES[INTRO_ISSUES.length - 1].title)}"
with: ${INTRO_OK_MESSAGE}`);
STEPS.map((step, i) => {
// The markdown string to look for in the comment from github-actions[bot]
const expectedString = `Results for \`${step.queryFile}\`: **correct** (${step.expectedResults} result${step.expectedResults === 1 ? '' : 's'})`;
const expectedIssue = `Results for \`${step.queryFile}\`:`;
console.log(`
- title: "${escapeDoubleQuoteYamlString(step.title)}"
description: "${escapeDoubleQuoteYamlString(step.description)}"
event: commit_comment.created
link: '{{ repoUrl }}/issues/'
actions:
# Ensure comment is posted by github-actions
- type: gate
left: '%payload.sender.login%'
operator: ===
right: github-actions[bot]
# Ensure comment is relevant for this issue
- type: gate
left: '%payload.comment.body%'
operator: search
# regex-escape then yaml-escape the expected markdown string
right: "/${escapeDoubleQuoteYamlString(escapeRegExp(expectedIssue))}/"
# Ensure comment has expected completed string
- type: gate
left: '%payload.comment.body%'
operator: search
# regex-escape then yaml-escape the expected markdown string
right: "/${escapeDoubleQuoteYamlString(escapeRegExp(expectedString))}/"
else:
- type: respond
issue: "${escapeDoubleQuoteYamlString(issueTitle(step, i))}"
with: ${FAIL_MESSAGE}
data:
commit: '%payload.comment.commit_id%'
commentUrl: '%payload.comment.html_url%'
# Answer is correct!!`);
/* The following is disabled for now as Learning Lab is using ^15.18.3 of
* octokit/rest.js, and listBranchesForHeadCommit was released in version
* v16.24.1
*/
// console.log(`
// # If there is a PR, merge it!
// - type: octokit
// method: repos.listBranchesForHeadCommit
// owner: '%payload.repository.owner.login%'
// repo: '%payload.repository.name%'
// commit_sha: '%payload.comment.commit_id%'
// action_id: get_branches
// - type: gate
// left: '%actions.get_branches.length%'
// operator: '!=='
// right: 1
// required: false
// else:
// # Executes when there is 1 matching branch
// - type: octokit
// method: api.pulls.list
// owner: '%payload.repository.owner.login%'
// repo: '%payload.repository.name%'
// head: '%payload.repository.owner.login%:%actions.get_branches.1.name%'
// action_id: get_prs
// - type: gate
// left: '%actions.get_prs.length%'
// operator: '!=='
// right: 1
// required: false
// else:
// # Executes when there is 1 matching pr
// - type: octokit
// method: api.pulls.merge
// owner: '%payload.repository.owner.login%'
// repo: '%payload.repository.name%'
// pull_number: '%actions.get_prs.1.number%'
// required: false
// `);
if (i < STEPS.length - 1) {
const next = STEPS[i + 1];
// Next Step
console.log(`
# Create Issue for next task
- type: createIssue
title: "${escapeDoubleQuoteYamlString(issueTitle(next, i + 1))}"
body: ${next.instructionsFile}
comments:`);
next.activitiesFiles.map(file => {
console.log(` - ${file}`)
})
console.log(` action_id: next_issue
- type: assignRegistrant
issue: '%actions.next_issue.data.number%'
# Make comment on current issue with link to commit that introduces correct query
- type: respond
issue: "${escapeDoubleQuoteYamlString(issueTitle(step, i))}"
with: ${NEXT_MESSAGE}
data:
next_issue: '%actions.next_issue.data.html_url%'
commit: '%payload.comment.commit_id%'
# Make comment on commit with link to next issue
- type: octokit
method: repos.createCommitComment
owner: '%payload.repository.owner.login%'
repo: '%payload.repository.name%'
sha: '%payload.comment.commit_id%'
body: |
Congratulations, looks like the query you introduced for step ${INTRO_ISSUES.length + i + 1} finds the correct results!
Merge this Pull Request (unless you're on the default branch), and take a look at the [instructions for the next step](%actions.next_issue.data.html_url%) to continue.
# Close current issue
- type: closeIssue
issue: "${escapeDoubleQuoteYamlString(issueTitle(step, i))}"`);
} else {
// End of course
console.log(`
# Make comment on current issue with final message
- type: respond
issue: "${escapeDoubleQuoteYamlString(issueTitle(step, i))}"
with: ${END_MESSAGE}
data:
commit: '%payload.comment.commit_id%'
# Close current issue
- type: closeIssue
issue: "${escapeDoubleQuoteYamlString(issueTitle(step, i))}"`);
}
})