Skip to content

Commit ab76d40

Browse files
committed
Initial commit: WPackagist Changelog Action
0 parents  commit ab76d40

File tree

4 files changed

+222
-0
lines changed

4 files changed

+222
-0
lines changed

.github/workflows/example.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: WPackagist Changelog
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'composer.lock'
7+
- 'composer.json'
8+
9+
jobs:
10+
changelog:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
pull-requests: write
14+
steps:
15+
- name: Comment changelogs on PR
16+
uses: roots/wpackagist-changelog-action@v1

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Roots Software LLC
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# WPackagist Changelog Action
2+
3+
Automatically comment WordPress plugin changelogs on pull requests when WPackagist dependencies change in your `composer.lock` or `composer.json` files.
4+
5+
## Usage
6+
7+
### Basic Setup
8+
9+
Create a workflow file in your repository (e.g., `.github/workflows/wpackagist-changelog.yml`):
10+
11+
```yaml
12+
name: WPackagist Changelog
13+
14+
on:
15+
pull_request:
16+
paths:
17+
- 'composer.lock'
18+
- 'composer.json'
19+
20+
jobs:
21+
changelog:
22+
runs-on: ubuntu-latest
23+
permissions:
24+
pull-requests: write
25+
steps:
26+
- name: Comment changelogs on PR
27+
uses: roots/wpackagist-changelog-action@v1
28+
```
29+
30+
## Example Comment
31+
32+
The action creates a formatted comment like this:
33+
34+
> # 🔌 WordPress Plugin Changelogs
35+
>
36+
> ## woocommerce
37+
>
38+
> <details>
39+
> <summary>View changelog</summary>
40+
>
41+
> #### 10.2.2 2025-09-29
42+
> **WooCommerce**
43+
> - Fix - Check if template part is from file system before building the result from file #61171
44+
> - Fix - Fix low-resolution images displayed in the Classic Template block gallery #61182
45+
> - Fix - Make legacy gallery filters available while rendering blocks #61173
46+
>
47+
> [View full changelog on WordPress.org](https://wordpress.org/plugins/woocommerce/#developers)
48+
>
49+
> </details>

action.yml

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
name: 'WPackagist Changelog Action'
2+
description: 'Automatically comment WordPress plugin changelogs on pull requests when WPackagist dependencies change'
3+
author: 'Roots Software LLC'
4+
branding:
5+
icon: 'package'
6+
color: 'blue'
7+
8+
inputs:
9+
token:
10+
description: 'GitHub token with permissions to comment on PRs'
11+
required: false
12+
default: ${{ github.token }}
13+
max-changelog-length:
14+
description: 'Maximum length of each plugin changelog (characters)'
15+
required: false
16+
default: '4000'
17+
max-comment-length:
18+
description: 'Maximum total comment length (characters)'
19+
required: false
20+
default: '60000'
21+
22+
runs:
23+
using: 'composite'
24+
steps:
25+
- name: Comment changelogs on PR
26+
uses: actions/github-script@v7
27+
env:
28+
MAX_CHANGELOG_LENGTH: ${{ inputs.max-changelog-length }}
29+
MAX_COMMENT_LENGTH: ${{ inputs.max-comment-length }}
30+
with:
31+
github-token: ${{ inputs.token }}
32+
script: |
33+
const MARKER = '<!-- wpackagist-changelog-bot -->';
34+
let plugins = new Set();
35+
36+
// Paginate through changed files (API approach - no git needed)
37+
let page = 1;
38+
const per_page = 100;
39+
while (true) {
40+
const { data: files } = await github.rest.pulls.listFiles({
41+
owner: context.repo.owner,
42+
repo: context.repo.repo,
43+
pull_number: context.issue.number,
44+
per_page,
45+
page,
46+
});
47+
if (!files.length) break;
48+
49+
for (const f of files) {
50+
if ((f.filename === 'composer.lock' || f.filename === 'composer.json') && f.patch) {
51+
const addedLines = f.patch.split('\n').filter(line => line.startsWith('+'));
52+
const addedContent = addedLines.join('\n');
53+
const matches = addedContent.match(/wpackagist-plugin\/([a-z0-9\-]+)/gi) || [];
54+
matches.forEach(m => plugins.add(m.toLowerCase().replace('wpackagist-plugin/', '')));
55+
}
56+
}
57+
if (files.length < per_page) break;
58+
page += 1;
59+
}
60+
61+
plugins = [...plugins].sort();
62+
63+
if (plugins.length === 0) {
64+
console.log('No WPackagist plugins changed');
65+
return;
66+
}
67+
68+
// Find existing bot comment via hidden marker
69+
const comments = await github.rest.issues.listComments({
70+
issue_number: context.issue.number,
71+
owner: context.repo.owner,
72+
repo: context.repo.repo,
73+
per_page: 100,
74+
});
75+
const botComment = comments.data.find(c => c.body?.includes(MARKER));
76+
77+
let comment = `${MARKER}\n# 🔌 WordPress Plugin Changelogs\n\n`;
78+
let totalLength = 0;
79+
const maxCommentLength = parseInt(process.env.MAX_COMMENT_LENGTH);
80+
const maxChangelogLength = parseInt(process.env.MAX_CHANGELOG_LENGTH);
81+
82+
for (const plugin of plugins) {
83+
try {
84+
const controller = new AbortController();
85+
const timeout = setTimeout(() => controller.abort(), 8000);
86+
const response = await fetch(
87+
`https://api.wordpress.org/plugins/info/1.0/${plugin}.json`,
88+
{ signal: controller.signal }
89+
);
90+
clearTimeout(timeout);
91+
92+
if (!response.ok) throw new Error('Plugin not found');
93+
94+
const data = await response.json();
95+
const changelog = data.sections?.changelog || 'No changelog available';
96+
97+
const pluginSection =
98+
`## ${plugin}\n\n` +
99+
`<details>\n<summary>View changelog</summary>\n\n` +
100+
changelog.substring(0, maxChangelogLength) +
101+
(changelog.length > maxChangelogLength ? '\n\n[...truncated]\n\n' : '\n\n') +
102+
`[View full changelog on WordPress.org](https://wordpress.org/plugins/${plugin}/#developers)\n\n` +
103+
`</details>\n\n`;
104+
105+
if (totalLength + pluginSection.length < maxCommentLength) {
106+
comment += pluginSection;
107+
totalLength += pluginSection.length;
108+
continue;
109+
}
110+
111+
comment += `\n\n⚠️ Additional plugins changed but comment size limit reached. Check composer files for details.\n`;
112+
break;
113+
} catch (error) {
114+
comment += `## ${plugin}\n\n⚠️ Could not fetch changelog (${error.message})\n\n`;
115+
}
116+
}
117+
118+
// Upsert comment
119+
if (botComment) {
120+
await github.rest.issues.updateComment({
121+
comment_id: botComment.id,
122+
owner: context.repo.owner,
123+
repo: context.repo.repo,
124+
body: comment
125+
});
126+
console.log('Updated existing comment');
127+
return;
128+
}
129+
130+
await github.rest.issues.createComment({
131+
issue_number: context.issue.number,
132+
owner: context.repo.owner,
133+
repo: context.repo.repo,
134+
body: comment
135+
});
136+
console.log('Created new comment');

0 commit comments

Comments
 (0)