Skip to content

Commit 5d9f87a

Browse files
committed
workflows
1 parent 61d0f6f commit 5d9f87a

8 files changed

Lines changed: 283 additions & 86 deletions

.github/scripts/check-wordpress-tested-up-to.py

Lines changed: 136 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from __future__ import annotations
55

6+
import argparse
67
import json
78
import os
89
import re
@@ -32,6 +33,7 @@
3233

3334

3435
def main() -> int:
36+
args = parse_args()
3537
latest_version = get_latest_wordpress_major_minor()
3638
excluded_dirs = get_excluded_dirs()
3739
findings = find_tested_up_to_entries(excluded_dirs)
@@ -42,41 +44,54 @@ def main() -> int:
4244
write_summary(latest_version, [], [message])
4345
return 1
4446

45-
failures = []
46-
for finding in findings:
47-
if finding["version"] is None:
48-
failures.append(
49-
f"{finding['path']}:{finding['line']}: Could not parse Tested up to version."
50-
)
51-
print_github_error(
52-
"Could not parse Tested up to version.",
53-
finding["path"],
54-
finding["line"],
55-
)
56-
continue
47+
failures = get_failures(latest_version, findings)
48+
updated_paths = []
5749

58-
tested_version = normalize_major_minor(finding["version"])
59-
if tested_version != latest_version:
60-
message = (
61-
f"Tested up to is {finding['version']}; expected {latest_version} "
62-
"for the latest WordPress release."
63-
)
64-
failures.append(f"{finding['path']}:{finding['line']}: {message}")
65-
print_github_error(message, finding["path"], finding["line"])
50+
if args.fix and failures:
51+
updated_paths = update_tested_up_to_entries(findings, latest_version)
52+
findings = find_tested_up_to_entries(excluded_dirs)
53+
failures = get_failures(latest_version, findings)
54+
55+
if failures:
56+
for failure in failures:
57+
print_github_error(failure["message"], failure["path"], failure["line"])
6658

67-
write_summary(latest_version, findings, failures)
59+
write_summary(latest_version, findings, failures, updated_paths)
6860

6961
if failures:
7062
entry_label = "entry" if len(failures) == 1 else "entries"
7163
print(f"Found {len(failures)} stale or invalid Tested up to {entry_label}.")
7264
for failure in failures:
73-
print(f"- {failure}")
65+
print(f"- {format_failure(failure)}")
7466
return 1
7567

68+
if updated_paths:
69+
path_label = "file" if len(updated_paths) == 1 else "files"
70+
print(
71+
f"Updated Tested up to metadata to WordPress {latest_version} "
72+
f"in {len(updated_paths)} {path_label}."
73+
)
74+
for path in updated_paths:
75+
print(f"- {path}")
76+
return 0
77+
7678
print(f"All Tested up to entries match WordPress {latest_version}.")
7779
return 0
7880

7981

82+
def parse_args() -> argparse.Namespace:
83+
parser = argparse.ArgumentParser(
84+
description='Check WordPress "Tested up to" metadata against the latest release.'
85+
)
86+
parser.add_argument(
87+
"--fix",
88+
action="store_true",
89+
help="Update stale or invalid Tested up to entries to the latest WordPress release.",
90+
)
91+
92+
return parser.parse_args()
93+
94+
8095
def get_latest_wordpress_major_minor() -> str:
8196
if WORDPRESS_LATEST_VERSION:
8297
return normalize_major_minor(WORDPRESS_LATEST_VERSION)
@@ -175,10 +190,100 @@ def should_scan(path: Path, excluded_dirs: set[str]) -> bool:
175190
return not any(part in excluded_dirs for part in path.parts)
176191

177192

193+
def get_failures(
194+
latest_version: str,
195+
findings: list[dict[str, str | int | None]],
196+
) -> list[dict[str, str | int]]:
197+
failures = []
198+
199+
for finding in findings:
200+
if finding["version"] is None:
201+
failures.append(
202+
{
203+
"path": str(finding["path"]),
204+
"line": int(finding["line"]),
205+
"message": "Could not parse Tested up to version.",
206+
}
207+
)
208+
continue
209+
210+
tested_version = normalize_major_minor(str(finding["version"]))
211+
if tested_version != latest_version:
212+
failures.append(
213+
{
214+
"path": str(finding["path"]),
215+
"line": int(finding["line"]),
216+
"message": (
217+
f"Tested up to is {finding['version']}; expected "
218+
f"{latest_version} for the latest WordPress release."
219+
),
220+
}
221+
)
222+
223+
return failures
224+
225+
226+
def update_tested_up_to_entries(
227+
findings: list[dict[str, str | int | None]],
228+
latest_version: str,
229+
) -> list[str]:
230+
paths_to_update = {
231+
str(finding["path"])
232+
for finding in findings
233+
if finding["version"] is None
234+
or normalize_major_minor(str(finding["version"])) != latest_version
235+
}
236+
237+
for path_string in paths_to_update:
238+
path_findings = [
239+
finding for finding in findings if str(finding["path"]) == path_string
240+
]
241+
path = Path(path_string)
242+
lines = path.read_text(encoding="utf-8", errors="replace").splitlines(
243+
keepends=True
244+
)
245+
246+
for finding in path_findings:
247+
line_index = int(finding["line"]) - 1
248+
lines[line_index] = replace_tested_up_to_line(
249+
lines[line_index], latest_version
250+
)
251+
252+
path.write_text("".join(lines), encoding="utf-8")
253+
254+
return sorted(paths_to_update)
255+
256+
257+
def replace_tested_up_to_line(line: str, latest_version: str) -> str:
258+
match = TESTED_UP_TO_PATTERN.search(line)
259+
if match:
260+
return f"{line[:match.start(1)]}{latest_version}{line[match.end(1):]}"
261+
262+
label_match = TESTED_UP_TO_LABEL_PATTERN.search(line)
263+
if not label_match:
264+
return line
265+
266+
line_ending = ""
267+
content = line
268+
if line.endswith("\r\n"):
269+
content = line[:-2]
270+
line_ending = "\r\n"
271+
elif line.endswith("\n"):
272+
content = line[:-1]
273+
line_ending = "\n"
274+
275+
return f"{content[:label_match.end()]} {latest_version}{line_ending}"
276+
277+
278+
def format_failure(failure: dict[str, str | int]) -> str:
279+
return f"{failure['path']}:{failure['line']}: {failure['message']}"
280+
281+
178282
def write_summary(
179283
latest_version: str,
180284
findings: list[dict[str, str | int | None]],
181-
failures: list[str],
285+
failures: list[dict[str, str | int] | str],
286+
updated_paths: list[str] | None = None,
182287
) -> None:
183288
summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
184289
if not summary_path:
@@ -194,7 +299,14 @@ def write_summary(
194299

195300
if failures:
196301
lines.append("## Failures")
197-
lines.extend(f"- {failure}" for failure in failures)
302+
lines.extend(
303+
f"- {format_failure(failure) if isinstance(failure, dict) else failure}"
304+
for failure in failures
305+
)
306+
elif updated_paths:
307+
lines.append("## Updates")
308+
lines.append(f"Updated Tested up to metadata in `{len(updated_paths)}` file(s).")
309+
lines.extend(f"- `{path}`" for path in updated_paths)
198310
else:
199311
lines.append("All Tested up to entries are current.")
200312

.github/workflows/branch-cleanup.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This workflow automatically deletes the branch associated with a pull request
22
# after it has been merged, unless the branch is protected (e.g., main, dev, staging).
3-
# It helps keep the repository clean by removing merged feature or bugfix branches.
3+
# It helps keep the repository clean by removing merged feature or bug-fix branches.
44

55
name: Branch Cleanup
66

@@ -12,16 +12,19 @@ permissions:
1212
contents: write
1313
pull-requests: read
1414

15+
env:
16+
PLUGIN_SLUG: enginescript-site-exporter
17+
1518
jobs:
1619
cleanup:
1720
runs-on: ubuntu-latest
18-
21+
1922
steps:
2023
- uses: actions/checkout@v6
2124
with:
2225
fetch-depth: 0
2326
token: ${{ secrets.GITHUB_TOKEN }}
24-
27+
2528
- name: Delete merged branch
2629
if: github.event.pull_request.merged == true && github.event.pull_request.head.repo.full_name == github.repository
2730
env:
@@ -30,14 +33,14 @@ jobs:
3033
REPOSITORY: ${{ github.repository }}
3134
run: |
3235
echo "Checking branch: $BRANCH_NAME"
33-
34-
# Protected branch check - using quotes to prevent injection
36+
37+
# Check protected branches. Quotes prevent injection.
3538
if [[ "$BRANCH_NAME" =~ ^(main|master|dev|develop|staging|production)$ ]]; then
3639
echo "::warning::Skipping deletion of protected branch: $BRANCH_NAME"
3740
exit 0
3841
fi
39-
40-
# Attempt branch deletion - using quotes to prevent injection
42+
43+
# Attempt branch deletion. Quotes prevent injection.
4144
echo "Attempting to delete branch: $BRANCH_NAME"
4245
if git push origin --delete "$BRANCH_NAME" 2>/dev/null; then
4346
echo "::notice::Successfully deleted branch: $BRANCH_NAME"

.github/workflows/issue-management.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ permissions:
1515
pull-requests: write
1616
issues: write
1717

18+
env:
19+
PLUGIN_SLUG: enginescript-site-exporter
20+
1821
jobs:
1922
triage:
2023
runs-on: ubuntu-latest

.github/workflows/new-issue.yml

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,46 @@
1-
# This workflow automatically posts a guidance comment on new issues.
2-
# It instructs users to run the `es.debug` command on their server and share the sanitized output,
3-
# helping maintainers get the information needed to troubleshoot and resolve issues efficiently.
1+
# This workflow automatically posts a guidance comment on new pull requests.
2+
# It welcomes contributors and provides a brief message to acknowledge their
3+
# contribution to EngineScript Site Exporter.
4+
# The workflow is triggered whenever a new pull request is opened.
45

5-
name: Issue Guidance
6+
name: New Pull Request Guidance
67

78
on:
8-
issues:
9+
# pull_request_target is used only so the workflow can post this static
10+
# guidance comment on forked PRs. Do not add checkout, build, or script steps
11+
# here because this event has access to repository-scoped credentials.
12+
pull_request_target:
913
types: [opened]
1014

11-
permissions:
12-
contents: write
13-
issues: write
15+
permissions: read-all
16+
17+
env:
18+
PLUGIN_SLUG: enginescript-site-exporter
1419

1520
jobs:
1621
guide:
1722
runs-on: ubuntu-latest
23+
# Explicitly define permissions required by the job
24+
permissions:
25+
pull-requests: write
1826
steps:
19-
- name: Post guidance comment
27+
- name: Post guidance comment on new PR
2028
uses: peter-evans/create-or-update-comment@v5
2129
with:
22-
issue-number: ${{ github.event.issue.number }}
30+
issue-number: ${{ github.event.pull_request.number }}
2331
body: |
24-
Thanks for opening an issue. Please provide a detailed description of the problem you're facing. If you have error messages or logs, please include them as well.
32+
Thanks for contributing to EngineScript Site Exporter!
33+
34+
**Before we review:**
35+
- [ ] Have you tested your changes with WordPress 6.8+?
36+
- [ ] Are your changes compatible with PHP 8.2+?
37+
- [ ] Have you followed WordPress coding standards?
38+
- [ ] Did you update the CHANGELOG.md if needed?
39+
40+
**Security Reminder**
41+
This plugin creates site export archives, so please ensure:
42+
- All user input is properly sanitized
43+
- All output is properly escaped
44+
- No security vulnerabilities are introduced
2545
26-
If you're server was configured with the main EngineScript application, you can run a detailed log by running the command `es.debug` in your server console. This will generate a detailed log of the current state of the server, which can help us understand the issue better.
46+
We'll review your PR soon.
Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
# This workflow automatically posts a guidance comment on new pull requests.
2-
# It encourages contributors to include sanitized output from `es.debug` if relevant,
3-
# helping maintainers review and test changes more efficiently.
2+
# It welcomes contributors and provides a brief message to acknowledge their
3+
# contribution to EngineScript Site Exporter.
4+
# The workflow is triggered whenever a new pull request is opened.
45

56
name: New Pull Request Guidance
67

78
on:
9+
# pull_request_target is used only so the workflow can post this static
10+
# guidance comment on forked PRs. Do not add checkout, build, or script steps
11+
# here because this event has access to repository-scoped credentials.
812
pull_request_target:
913
types: [opened]
1014

15+
permissions: read-all
16+
17+
env:
18+
PLUGIN_SLUG: enginescript-site-exporter
19+
1120
jobs:
1221
guide:
1322
runs-on: ubuntu-latest
@@ -20,18 +29,18 @@ jobs:
2029
with:
2130
issue-number: ${{ github.event.pull_request.number }}
2231
body: |
23-
Thanks for contributing to EngineScript Site Exporter! 🎉
32+
Thanks for contributing to EngineScript Site Exporter!
2433
2534
**Before we review:**
2635
- [ ] Have you tested your changes with WordPress 6.8+?
2736
- [ ] Are your changes compatible with PHP 8.2+?
2837
- [ ] Have you followed WordPress coding standards?
2938
- [ ] Did you update the CHANGELOG.md if needed?
30-
31-
**Security Reminder:**
32-
This plugin handles sensitive site databases, so please ensure:
33-
- All user inputs are properly sanitized
34-
- All outputs are properly escaped
39+
40+
**Security Reminder**
41+
This plugin creates site export archives, so please ensure:
42+
- All user input is properly sanitized
43+
- All output is properly escaped
3544
- No security vulnerabilities are introduced
36-
37-
We'll review your PR soon! 🚀
45+
46+
We'll review your PR soon.

0 commit comments

Comments
 (0)