Skip to content

Commit 0c6d3ee

Browse files
ci: check project url
1 parent 3f60add commit 0c6d3ee

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
on:
2+
issues:
3+
types: [opened, edited]
4+
jobs:
5+
checkProjectURL:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/github-script@v7
9+
name: Check for StackBlitz or Bolt reproduction link
10+
with:
11+
script: |
12+
const issue = await github.rest.issues.get({
13+
issue_number: context.issue.number,
14+
owner: context.repo.owner,
15+
repo: context.repo.repo,
16+
});
17+
18+
const projectInfo = parseProjectInfo(issue.data.body ?? '');
19+
20+
if (!projectInfo) {
21+
// if no project info is found, we can assume that the user did not provide a StackBlitz or Bolt reproduction link
22+
await comment(issue.data, 'We noticed that you did not provide a StackBlitz or Bolt reproduction link. Please update the issue and add a link to a public project.');
23+
24+
return;
25+
}
26+
27+
const projectExists = await checkProject(projectInfo);
28+
29+
if (!projectExists) {
30+
// if the response is not ok, we can assume that the project is not public
31+
await comment(issue.data, 'We noticed that the project you shared is not public. Please change the visibility of the project to either secret or public.');
32+
33+
return;
34+
}
35+
36+
// we can remove the label in case it was added before
37+
await removeLabel();
38+
39+
function parseProjectInfo(comment) {
40+
const reproRegex = /https:\/\/(?:stackblitz\.com|bolt\.new)\/(?:(?:~|c)\/)?(?:edit\/)?(?<slug>[a-zA-Z0-9-]+)/i;
41+
42+
const { slug } = comment.match(reproRegex)?.groups || {};
43+
44+
if (!slug) {
45+
return null;
46+
}
47+
48+
// if the slug is `github`, we test if a GitHub link is provided
49+
if (slug === 'github') {
50+
const githubRegex = /https:\/\/stackblitz\.com\/(?:~\/(?:github\.com|github_project)|github)\/(?<owner>[\w\-_]+)\/(?<repo>[\w\-_]+)/i;
51+
52+
const { owner, repo } = comment.match(githubRegex)?.groups || {};
53+
54+
if (owner && repo) {
55+
return { type: 'github', owner, repo };
56+
}
57+
}
58+
59+
return { type: 'stackblitz', slug };
60+
}
61+
62+
async function checkProject(projectInfo) {
63+
if (projectInfo.type === 'github') {
64+
const { owner, repo } = projectInfo;
65+
66+
try {
67+
const response = await github.rest.repos.get({
68+
owner,
69+
repo,
70+
});
71+
72+
return response.status === 200;
73+
} catch {
74+
return false;
75+
}
76+
}
77+
78+
if (projectInfo.type === 'stackblitz') {
79+
const { slug } = projectInfo;
80+
81+
const response = await fetch(`https://stackblitz.com/api/projects/${slug}`, {
82+
method: 'HEAD',
83+
});
84+
85+
return response.ok;
86+
}
87+
88+
return false;
89+
}
90+
91+
async function comment(issue, body) {
92+
if (issue.labels.some((label) => label.name === 'needs reproduction')) {
93+
// do not comment or add the label again if we already have the label
94+
return;
95+
}
96+
97+
await Promise.allSettled([
98+
github.rest.issues.createComment({
99+
issue_number: context.issue.number,
100+
owner: context.repo.owner,
101+
repo: context.repo.repo,
102+
body,
103+
}),
104+
github.rest.issues.addLabels({
105+
issue_number: context.issue.number,
106+
owner: context.repo.owner,
107+
repo: context.repo.repo,
108+
labels: ['needs reproduction'],
109+
})
110+
]);
111+
}
112+
113+
async function removeLabel() {
114+
await github.rest.issues.removeLabel({
115+
issue_number: context.issue.number,
116+
owner: context.repo.owner,
117+
repo: context.repo.repo,
118+
name: 'needs reproduction',
119+
});
120+
}

0 commit comments

Comments
 (0)