Skip to content

Commit 2b3968d

Browse files
JiashuJiashu
authored andcommitted
Add advanced fast-track workflow
1 parent dbe048e commit 2b3968d

2 files changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
- Create `my-first-branch` from `main`.
2+
- Create `PROFILE.md` in the repository root on `my-first-branch`.
3+
- Add `Welcome to my GitHub profile!` to `PROFILE.md`.
4+
- Commit the changes on `my-first-branch` with the message `Add PROFILE.md`.
5+
- Open a pull request from `my-first-branch` into `main`.
6+
- Set the pull request title to `Add my first file`.
7+
- Add a non-empty description to the pull request.
8+
- Merge the pull request into `main`.
9+
- Delete `my-first-branch` after the pull request is merged.
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
name: Advanced Fast Track
2+
3+
on:
4+
create:
5+
delete:
6+
push:
7+
branches:
8+
- my-first-branch
9+
pull_request:
10+
branches:
11+
- main
12+
types:
13+
- opened
14+
- edited
15+
- synchronize
16+
- reopened
17+
- closed
18+
workflow_dispatch:
19+
20+
permissions:
21+
contents: read
22+
issues: write
23+
pull-requests: read
24+
25+
concurrency:
26+
group: advanced-fast-track
27+
cancel-in-progress: false
28+
29+
jobs:
30+
find_exercise:
31+
name: Find Exercise Issue
32+
uses: skills/exercise-toolkit/.github/workflows/find-exercise-issue.yml@v0.7.0
33+
34+
evaluate:
35+
name: Evaluate exercise progress
36+
needs: find_exercise
37+
if: ${{ !github.event.repository.is_template }}
38+
runs-on: ubuntu-latest
39+
env:
40+
ISSUE_NUMBER: ${{ needs.find_exercise.outputs.issue-number }}
41+
steps:
42+
- name: Checkout task list
43+
uses: actions/checkout@v4
44+
45+
- name: Evaluate tasks and update progress
46+
uses: actions/github-script@v7
47+
with:
48+
script: |
49+
const issueNumber = Number(process.env.ISSUE_NUMBER);
50+
const owner = context.repo.owner;
51+
const repo = context.repo.repo;
52+
const stateMarker = '<!-- advanced-fast-track-state -->';
53+
const taskFile = '.github/steps/advanced-task-list.md';
54+
const tasks = require('fs')
55+
.readFileSync(taskFile, 'utf8')
56+
.split('\n')
57+
.filter((line) => line.startsWith('- '))
58+
.map((line) => line.slice(2));
59+
60+
const comments = await github.paginate(github.rest.issues.listComments, {
61+
owner,
62+
repo,
63+
issue_number: issueNumber,
64+
per_page: 100,
65+
});
66+
const stateComment = comments.find((comment) => comment.body?.includes(stateMarker));
67+
let frontier = 0;
68+
69+
if (stateComment) {
70+
const match = stateComment.body.match(/frontier:\s*(\d+)/);
71+
frontier = match ? Number(match[1]) : 0;
72+
}
73+
74+
async function branchExists(branch) {
75+
try {
76+
await github.rest.repos.getBranch({ owner, repo, branch });
77+
return true;
78+
} catch (error) {
79+
if (error.status === 404) return false;
80+
throw error;
81+
}
82+
}
83+
84+
async function profileContentIsCorrect() {
85+
try {
86+
const response = await github.rest.repos.getContent({
87+
owner,
88+
repo,
89+
path: 'PROFILE.md',
90+
ref: 'my-first-branch',
91+
});
92+
return response.data.type === 'file' &&
93+
Buffer.from(response.data.content, 'base64').toString('utf8').trim() ===
94+
'Welcome to my GitHub profile!';
95+
} catch (error) {
96+
if (error.status === 404) return false;
97+
throw error;
98+
}
99+
}
100+
101+
async function profileCommitExists() {
102+
if (!(await branchExists('my-first-branch'))) return false;
103+
const commits = await github.paginate(github.rest.repos.listCommits, {
104+
owner,
105+
repo,
106+
sha: 'my-first-branch',
107+
path: 'PROFILE.md',
108+
per_page: 100,
109+
});
110+
return commits.some((commit) => commit.commit.message.trim() === 'Add PROFILE.md');
111+
}
112+
113+
async function findPullRequest() {
114+
const response = await github.rest.pulls.list({
115+
owner,
116+
repo,
117+
state: 'all',
118+
base: 'main',
119+
head: `${owner}:my-first-branch`,
120+
per_page: 100,
121+
});
122+
return response.data[0];
123+
}
124+
125+
async function taskIsComplete(taskNumber) {
126+
if (taskNumber === 1) return branchExists('my-first-branch');
127+
if (taskNumber === 2) return profileContentIsCorrect();
128+
if (taskNumber === 3) return profileContentIsCorrect();
129+
if (taskNumber === 4) return profileCommitExists();
130+
131+
const pullRequest = await findPullRequest();
132+
if (taskNumber === 5) return Boolean(pullRequest);
133+
if (taskNumber === 6) {
134+
return Boolean(pullRequest?.title.toLowerCase().includes('add my first file'));
135+
}
136+
if (taskNumber === 7) return Boolean(pullRequest?.body?.trim());
137+
if (taskNumber === 8) return Boolean(pullRequest?.merged_at);
138+
if (taskNumber === 9) return Boolean(pullRequest?.merged_at) &&
139+
!(await branchExists('my-first-branch'));
140+
return false;
141+
}
142+
143+
while (frontier < tasks.length && await taskIsComplete(frontier + 1)) {
144+
frontier += 1;
145+
}
146+
147+
const progress = tasks.map((task, index) =>
148+
`${index < frontier ? '- [x]' : '- [ ]'} ${task}`
149+
).join('\n');
150+
const body = [
151+
stateMarker,
152+
`frontier: ${frontier}`,
153+
'',
154+
'### Advanced Fast Track progress',
155+
'',
156+
progress,
157+
].join('\n');
158+
159+
if (stateComment) {
160+
await github.rest.issues.updateComment({
161+
owner,
162+
repo,
163+
comment_id: stateComment.id,
164+
body,
165+
});
166+
} else {
167+
await github.rest.issues.createComment({
168+
owner,
169+
repo,
170+
issue_number: issueNumber,
171+
body,
172+
});
173+
}
174+
175+
if (frontier === tasks.length) {
176+
await github.rest.issues.update({
177+
owner,
178+
repo,
179+
issue_number: issueNumber,
180+
state: 'closed',
181+
});
182+
}
183+
184+
core.info(`Advanced Fast Track frontier: ${frontier}/${tasks.length}`);

0 commit comments

Comments
 (0)