Skip to content

Commit 30fe26d

Browse files
Merge master into dependabot/npm_and_yarn/assets/create-glee-app/templates/tutorial/babel/helpers-7.26.10
2 parents 802d3ce + 6902e04 commit 30fe26d

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

.github/workflows/auto-changeset.yml

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Changeset Automation
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- opened
7+
- edited
8+
- synchronize
9+
- reopened
10+
- ready_for_review
11+
12+
jobs:
13+
auto-changeset:
14+
if: |
15+
startsWith(github.repository, 'asyncapi/') &&
16+
(!contains(github.event.pull_request.labels, 'skip-changeset')) &&
17+
( startsWith(github.event.pull_request.title, 'fix:') ||
18+
startsWith(github.event.pull_request.title, 'feat:') ||
19+
startsWith(github.event.pull_request.title, 'fix!:') ||
20+
startsWith(github.event.pull_request.title, 'feat!:')
21+
)
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout Repository
25+
uses: actions/checkout@v4
26+
with:
27+
token: ${{ secrets.GH_TOKEN }}
28+
29+
- name: Checkout PR
30+
env:
31+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
32+
run: gh pr checkout ${{ github.event.pull_request.number }}
33+
34+
# This step has been added in such a way because
35+
# - Using simple `npm install` will install all the packages in the repo, which takes a lot of time.
36+
# - Using `npm install` doesn't work with all repositories, as some of them have a different structure/package manager.
37+
# - Global installation also doesn't work as can't be imported in the script.
38+
- name: Install specific package in temp dir
39+
run: |
40+
mkdir temp-install
41+
cd temp-install
42+
npm init -y
43+
npm install [email protected]
44+
cp -r node_modules ../node_modules
45+
cd ..
46+
rm -rf temp-install
47+
48+
- name: Get changeset contents
49+
id: get_changeset_contents
50+
uses: actions/github-script@v7
51+
with:
52+
script: |
53+
const { getChangesetContents } = require('./.github/workflows/changeset-utils/index.js')
54+
const pullRequest = context.payload.pull_request;
55+
const changesetContents = await getChangesetContents(pullRequest, github)
56+
return changesetContents;
57+
58+
- name: Create changeset file
59+
run: "echo -e ${{ steps.get_changeset_contents.outputs.result }} > .changeset/${{ github.event.pull_request.number }}.md"
60+
61+
- name: Commit changeset file
62+
run: |
63+
git config --global user.name asyncapi-bot
64+
git config --global user.email [email protected]
65+
git add .changeset/${{ github.event.pull_request.number }}.md
66+
# Check if there are any changes to commit
67+
if git diff --quiet HEAD; then
68+
echo "No changes to commit"
69+
else
70+
git commit -m "chore: add changeset for PR #${{ github.event.pull_request.number }}"
71+
fi
72+
73+
- name: Push changeset file
74+
env:
75+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
76+
run: |
77+
git remote set-url origin https://github.com/${{ github.event.pull_request.head.repo.full_name }}
78+
git push origin HEAD:${{ github.event.pull_request.head.ref }}
79+
80+
- name: Comment workflow
81+
env:
82+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
83+
uses: actions/github-script@v7
84+
with:
85+
script: |
86+
const { commentWorkflow } = require('./.github/workflows/changeset-utils/index.js')
87+
const pullRequest = context.payload.pull_request;
88+
const changesetContents = ${{ steps.get_changeset_contents.outputs.result }}
89+
await commentWorkflow(pullRequest, github, changesetContents)
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
const path = require('path');
2+
const { readPackageUp } = require('read-package-up');
3+
4+
// @todo
5+
// Check if maintainer can modify the head
6+
7+
const getFormattedCommits = async (pullRequest, github) => {
8+
const commitOpts = github.rest.pulls.listCommits.endpoint.merge({
9+
owner: pullRequest.base.repo.owner.login,
10+
repo: pullRequest.base.repo.name,
11+
pull_number: pullRequest.number,
12+
});
13+
14+
const commits = await github.paginate(commitOpts);
15+
16+
// Filter merge commits and commits by asyncapi-bot
17+
const filteredCommits = commits.filter((commit) => {
18+
return !commit.commit.message.startsWith('Merge pull request') && !commit.commit.message.startsWith('Merge branch') && !commit.commit.author.name.startsWith('asyncapi-bot') && !commit.commit.author.name.startsWith('dependabot[bot]');
19+
});
20+
21+
return filteredCommits.map((commit) => {
22+
return {
23+
commit_sha: commit.sha.slice(0, 7), // first 7 characters of the commit sha is enough to identify the commit
24+
commit_message: commit.commit.message,
25+
};
26+
});
27+
}
28+
29+
const getReleasedPackages = async (pullRequest, github) => {
30+
const files = await github.paginate(github.rest.pulls.listFiles.endpoint.merge({
31+
owner: pullRequest.base.repo.owner.login,
32+
repo: pullRequest.base.repo.name,
33+
pull_number: pullRequest.number,
34+
}));
35+
36+
const releasedPackages = [];
37+
const ignoredFiles = ['README.md', 'CHANGELOG.md', './changeset/README.md', 'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'];
38+
for (const file of files) {
39+
if (!ignoredFiles.includes(file.filename)) {
40+
const cwd = path.resolve(path.dirname(file.filename));
41+
const pack = await readPackageUp({ cwd });
42+
if (pack && pack?.packageJson?.name && !releasedPackages.includes(pack.packageJson.name)) {
43+
releasedPackages.push(pack.packageJson.name);
44+
}
45+
}
46+
}
47+
48+
console.debug('Filenames', files.map((file) => file.filename));
49+
return releasedPackages;
50+
}
51+
52+
const getReleaseNotes = async (pullRequest, github) => {
53+
const commits = await getFormattedCommits(pullRequest, github);
54+
/**
55+
* Release notes are generated from the commits.
56+
* Format:
57+
* - title
58+
* - commit_sha: commit_message (Array of commits)
59+
*/
60+
const releaseNotes = pullRequest.title + '\n\n' + commits.map((commit) => {
61+
return `- ${commit.commit_sha}: ${commit.commit_message}`;
62+
}).join('\n');
63+
64+
return releaseNotes;
65+
}
66+
67+
const getChangesetContents = async (pullRequest, github) => {
68+
const title = pullRequest.title;
69+
const releaseType = title.split(':')[0];
70+
let releaseVersion = 'patch';
71+
switch (releaseType) {
72+
case 'fix':
73+
releaseVersion = 'patch';
74+
case 'feat':
75+
releaseVersion = 'minor';
76+
case 'fix!':
77+
releaseVersion = 'major';
78+
case 'feat!':
79+
releaseVersion = 'major';
80+
default:
81+
releaseVersion = 'patch';
82+
}
83+
84+
const releaseNotes = await getReleaseNotes(pullRequest, github);
85+
const releasedPackages = await getReleasedPackages(pullRequest, github);
86+
87+
if (releasedPackages.length === 0) {
88+
console.debug('No packages released');
89+
return '';
90+
}
91+
console.debug('Released packages', releasedPackages);
92+
console.debug('Release notes', releaseNotes);
93+
94+
const changesetContents = `---\n` + releasedPackages.map((pkg) => {
95+
return `'${pkg}': ${releaseVersion}`;
96+
}).join('\n') + `\n---\n\n${releaseNotes}\n\n`
97+
98+
return changesetContents;
99+
};
100+
101+
/**
102+
* This function checks if a comment has already been created by the workflow.
103+
* If not, it creates a comment with the changeset.
104+
* If it is already created, it updates the comment with the new changeset.
105+
*/
106+
const commentWorkflow = async (pullRequest, github, changesetContents) => {
107+
const body = `#### Changeset has been generated for this PR as part of auto-changeset workflow.\n\n<details><summary>Please review the changeset before merging the PR.</summary>\n\n\`\`\`\n${changesetContents}\`\`\`\n\n</details>\n\n[If you are a maintainer or the author of the PR, you can change the changeset by clicking here](https://github.com/${pullRequest.head.repo.full_name}/edit/${pullRequest.head.ref}/.changeset/${pullRequest.number}.md)\n\n> [!TIP]\n> If you don't want auto-changeset to run on this PR, you can add the label \`skip-changeset\` to the PR or remove the changeset and change PR title to something other than \`fix:\` or \`feat:\`.`;
108+
109+
const comments = await github.rest.issues.listComments({
110+
owner: pullRequest.base.repo.owner.login,
111+
repo: pullRequest.base.repo.name,
112+
issue_number: pullRequest.number,
113+
});
114+
115+
const comment = comments.data.find((comment) => comment.body.includes('Changeset has been generated for this PR as part of auto-changeset workflow.'));
116+
if (comment) {
117+
await github.rest.issues.updateComment({
118+
owner: pullRequest.base.repo.owner.login,
119+
repo: pullRequest.base.repo.name,
120+
comment_id: comment.id,
121+
body: body,
122+
});
123+
} else {
124+
await github.rest.issues.createComment({
125+
owner: pullRequest.base.repo.owner.login,
126+
repo: pullRequest.base.repo.name,
127+
issue_number: pullRequest.number,
128+
body: body,
129+
user: 'asyncapi-bot',
130+
});
131+
}
132+
}
133+
134+
module.exports = {
135+
getChangesetContents,
136+
commentWorkflow,
137+
};

0 commit comments

Comments
 (0)