Skip to content

Commit 1ea223f

Browse files
Merge pull request #1104 from reportportal/feature/EPMRPP-114901-Add-a-trigger-for-RP-docs-rebuild-on-release-creation-update
EPMRPP-114901 || Add a trigger for RP docs rebuild on release creation/update
1 parent 3e3ff72 commit 1ea223f

7 files changed

Lines changed: 301 additions & 62 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2026 EPAM Systems
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
name: Delete Release Page
15+
16+
on:
17+
repository_dispatch:
18+
types: [release-deleted]
19+
20+
jobs:
21+
delete-release:
22+
runs-on: ubuntu-latest
23+
permissions:
24+
contents: write
25+
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@v4
29+
with:
30+
token: ${{ secrets.GH_TOKEN }}
31+
32+
- name: Set up Node.js
33+
uses: actions/setup-node@v4
34+
with:
35+
node-version: 20
36+
37+
- name: Delete release page
38+
run: node scripts/delete-release.js
39+
env:
40+
RELEASE_NAME: ${{ github.event.client_payload.release_name }}
41+
42+
- name: Check for changes
43+
id: changes
44+
run: |
45+
if [ -n "$(git status docs/releases/ --porcelain)" ]; then
46+
echo "has_changes=true" >> "$GITHUB_OUTPUT"
47+
else
48+
echo "has_changes=false" >> "$GITHUB_OUTPUT"
49+
echo "No release file was deleted."
50+
fi
51+
52+
- name: Commit and push deletion
53+
if: steps.changes.outputs.has_changes == 'true'
54+
env:
55+
RELEASE_NAME: ${{ github.event.client_payload.release_name }}
56+
run: |
57+
git config user.name "github-actions[bot]"
58+
git config user.email "github-actions[bot]@users.noreply.github.com"
59+
git add docs/releases/
60+
git commit -m "docs: remove release page for $RELEASE_NAME"
61+
git push

.github/workflows/sync-releases.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ on:
2020
paths-ignore:
2121
- 'docs/releases/**'
2222

23+
repository_dispatch:
24+
types: [release-published]
25+
2326
workflow_call:
2427
inputs:
2528
scope:
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2026 EPAM Systems
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
name: Update Release Page
15+
16+
on:
17+
repository_dispatch:
18+
types: [release-edited]
19+
20+
jobs:
21+
update-release:
22+
runs-on: ubuntu-latest
23+
permissions:
24+
contents: write
25+
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@v4
29+
with:
30+
token: ${{ secrets.GH_TOKEN }}
31+
32+
- name: Set up Node.js
33+
uses: actions/setup-node@v4
34+
with:
35+
node-version: 20
36+
37+
- name: Update release page
38+
run: node scripts/update-release.js
39+
env:
40+
GH_TOKEN: ${{ secrets.GH_TOKEN }}
41+
RELEASE_NAME: ${{ github.event.client_payload.release_name }}
42+
RELEASE_TAG: ${{ github.event.client_payload.release_tag }}
43+
44+
- name: Check for changes
45+
id: changes
46+
run: |
47+
if [ -n "$(git status docs/releases/ --porcelain)" ]; then
48+
echo "has_changes=true" >> "$GITHUB_OUTPUT"
49+
else
50+
echo "has_changes=false" >> "$GITHUB_OUTPUT"
51+
echo "No changes to release page."
52+
fi
53+
54+
- name: Commit and push updated release page
55+
if: steps.changes.outputs.has_changes == 'true'
56+
env:
57+
RELEASE_TAG: ${{ github.event.client_payload.release_tag }}
58+
run: |
59+
git config user.name "github-actions[bot]"
60+
git config user.email "github-actions[bot]@users.noreply.github.com"
61+
git add docs/releases/
62+
git commit -m "docs: update release page for $RELEASE_TAG"
63+
git push

scripts/delete-release.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const { buildFileName } = require('./release-utils');
4+
5+
const RELEASES_DIR = path.join(__dirname, '..', 'docs', 'releases');
6+
7+
async function main() {
8+
const releaseName = process.env.RELEASE_NAME?.trim();
9+
10+
if (!releaseName) {
11+
throw new Error('RELEASE_NAME env var is required');
12+
}
13+
14+
const fileName = buildFileName(releaseName);
15+
const filePath = path.join(RELEASES_DIR, fileName);
16+
17+
if (!fs.existsSync(filePath)) {
18+
console.warn(`Warning: release file not found, nothing to delete: ${fileName}`);
19+
return;
20+
}
21+
22+
fs.unlinkSync(filePath);
23+
console.log(`Deleted: ${fileName}`);
24+
}
25+
26+
main().catch((err) => {
27+
console.error(err);
28+
process.exit(1);
29+
});

scripts/release-utils.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
function sanitizeFileToken(value) {
2+
return value
3+
.replace(/[\\/:*?"<>|]/g, '-')
4+
.replace(/\.\.(\/|\\)?/g, '')
5+
.trim();
6+
}
7+
8+
function buildFileName(name) {
9+
let v = sanitizeFileToken(stripPrefix(name));
10+
11+
if (/^BETA/i.test(v)) {
12+
const nums = v.match(/[\d.]+/);
13+
return nums ? `Version${nums[0]}RC.md` : `Version${sanitizeFileToken(v).replace(/\s+/g, '')}.md`;
14+
}
15+
16+
v = v.replace(/\s+(Final|RC|Beta|Alpha)$/i, '').trim();
17+
18+
return `Version${sanitizeFileToken(v)}.md`;
19+
}
20+
21+
function buildSidebarLabel(name) {
22+
let v = stripPrefix(name);
23+
24+
if (/^BETA/i.test(v)) {
25+
const nums = v.match(/[\d.]+/);
26+
return nums ? `Version ${nums[0]} RC` : `Version ${v}`;
27+
}
28+
29+
return `Version ${v}`;
30+
}
31+
32+
function transformBody(body) {
33+
let result = body;
34+
35+
result = result.replace(/\r\n/g, '\n');
36+
37+
result = result.replace(/<img\b[^>]*>/gi, (tag) => {
38+
const srcMatch = tag.match(/src="([^"]+)"/i);
39+
const altMatch = tag.match(/alt="([^"]*)"/i);
40+
const src = srcMatch ? srcMatch[1] : '';
41+
const alt = altMatch ? altMatch[1] : 'image';
42+
return src ? `![${alt}](${src})` : '';
43+
});
44+
45+
result = result.replace(
46+
/(?<!["\(])(?<!\]\()https?:\/\/[^\s)<>\]]+/g,
47+
(url) => `[${extractLabel(url)}](${url})`,
48+
);
49+
50+
return result;
51+
}
52+
53+
function extractLabel(url) {
54+
try {
55+
const parts = new URL(url).pathname.split('/').filter(Boolean);
56+
return parts.length > 0 ? parts[parts.length - 1] : url;
57+
} catch {
58+
return url;
59+
}
60+
}
61+
62+
function stripPrefix(name) {
63+
return name
64+
.replace(/^Release\s+/i, '')
65+
.replace(/^ReportPortal\s+/i, '')
66+
.replace(/^v\.?\s*/i, '')
67+
.trim();
68+
}
69+
70+
module.exports = { buildFileName, buildSidebarLabel, transformBody, extractLabel, stripPrefix };

scripts/sync-releases.js

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs');
22
const path = require('path');
3+
const { buildFileName, buildSidebarLabel, transformBody } = require('./release-utils');
34

45
const RELEASES_API_URL =
56
process.env.RELEASES_URL ||
@@ -132,68 +133,6 @@ async function fetchAllReleases() {
132133
return all;
133134
}
134135

135-
function transformBody(body) {
136-
let result = body;
137-
138-
result = result.replace(/\r\n/g, '\n');
139-
140-
result = result.replace(/<img\b[^>]*>/gi, (tag) => {
141-
const srcMatch = tag.match(/src="([^"]+)"/i);
142-
const altMatch = tag.match(/alt="([^"]*)"/i);
143-
const src = srcMatch ? srcMatch[1] : '';
144-
const alt = altMatch ? altMatch[1] : 'image';
145-
return src ? `![${alt}](${src})` : '';
146-
});
147-
148-
result = result.replace(
149-
/(?<!["\(])(?<!\]\()https?:\/\/[^\s)<>\]]+/g,
150-
(url) => `[${extractLabel(url)}](${url})`,
151-
);
152-
153-
return result;
154-
}
155-
156-
function extractLabel(url) {
157-
try {
158-
const parts = new URL(url).pathname.split('/').filter(Boolean);
159-
return parts.length > 0 ? parts[parts.length - 1] : url;
160-
} catch {
161-
return url;
162-
}
163-
}
164-
165-
function buildFileName(name) {
166-
let v = stripPrefix(name);
167-
168-
if (/^BETA/i.test(v)) {
169-
const nums = v.match(/[\d.]+/);
170-
return nums ? `Version${nums[0]}RC.md` : `Version${v.replace(/\s+/g, '')}.md`;
171-
}
172-
173-
v = v.replace(/\s+(Final|RC|Beta|Alpha)$/i, '').trim();
174-
175-
return `Version${v}.md`;
176-
}
177-
178-
function buildSidebarLabel(name) {
179-
let v = stripPrefix(name);
180-
181-
if (/^BETA/i.test(v)) {
182-
const nums = v.match(/[\d.]+/);
183-
return nums ? `Version ${nums[0]} RC` : `Version ${v}`;
184-
}
185-
186-
return `Version ${v}`;
187-
}
188-
189-
function stripPrefix(name) {
190-
return name
191-
.replace(/^Release\s+/i, '')
192-
.replace(/^ReportPortal\s+/i, '')
193-
.replace(/^v\.?\s*/i, '')
194-
.trim();
195-
}
196-
197136
main().catch((err) => {
198137
console.error(err);
199138
process.exit(1);

scripts/update-release.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const { buildFileName, buildSidebarLabel, transformBody } = require('./release-utils');
4+
5+
const RELEASES_DIR = path.join(__dirname, '..', 'docs', 'releases');
6+
const BASE_API_URL = 'https://api.github.com/repos/reportportal/reportportal/releases/tags';
7+
8+
async function main() {
9+
const releaseName = process.env.RELEASE_NAME?.trim();
10+
const releaseTag = process.env.RELEASE_TAG?.trim();
11+
12+
if (!releaseName || !releaseTag) {
13+
throw new Error('RELEASE_NAME and RELEASE_TAG env vars are required');
14+
}
15+
16+
const release = await fetchReleaseByTag(releaseTag);
17+
18+
const fileName = buildFileName(releaseName);
19+
const filePath = path.join(RELEASES_DIR, fileName);
20+
21+
const sidebarPosition = readExistingSidebarPosition(filePath);
22+
const sidebarLabel = buildSidebarLabel(releaseName);
23+
const body = transformBody(release.body || '');
24+
25+
const content = [
26+
'---',
27+
`sidebar_position: ${sidebarPosition}`,
28+
`sidebar_label: ${sidebarLabel}`,
29+
'---',
30+
'',
31+
`# ${sidebarLabel}`,
32+
'',
33+
body,
34+
'',
35+
].join('\n');
36+
37+
fs.mkdirSync(RELEASES_DIR, { recursive: true });
38+
fs.writeFileSync(filePath, content, 'utf-8');
39+
console.log(`Updated: ${fileName}`);
40+
}
41+
42+
function readExistingSidebarPosition(filePath) {
43+
if (!fs.existsSync(filePath)) return 1;
44+
const text = fs.readFileSync(filePath, 'utf-8');
45+
const match = text.match(/^sidebar_position:\s*(\d+)/m);
46+
return match ? parseInt(match[1], 10) : 1;
47+
}
48+
49+
async function fetchReleaseByTag(tag) {
50+
const url = `${BASE_API_URL}/${encodeURIComponent(tag)}`;
51+
const headers = {
52+
Accept: 'application/vnd.github+json',
53+
'X-GitHub-Api-Version': '2022-11-28',
54+
'User-Agent': 'reportportal-docs-sync',
55+
};
56+
57+
if (process.env.GH_TOKEN) {
58+
headers.Authorization = `Bearer ${process.env.GH_TOKEN}`;
59+
} else {
60+
console.warn('GH_TOKEN not set - using unauthenticated API (60 req/hr limit)');
61+
}
62+
63+
const res = await fetch(url, { headers });
64+
if (!res.ok) {
65+
throw new Error(`GitHub API ${res.status}: ${res.statusText}`);
66+
}
67+
68+
return res.json();
69+
}
70+
71+
main().catch((err) => {
72+
console.error(err);
73+
process.exit(1);
74+
});

0 commit comments

Comments
 (0)