Skip to content

EPMRPP-89449 || Load release versions from GitHub#1068

Open
maria-hambardzumian wants to merge 1 commit intodevelopfrom
feature/EPMRPP-89449-Load-release-versions-from-GitHub
Open

EPMRPP-89449 || Load release versions from GitHub#1068
maria-hambardzumian wants to merge 1 commit intodevelopfrom
feature/EPMRPP-89449-Load-release-versions-from-GitHub

Conversation

@maria-hambardzumian
Copy link
Contributor

@maria-hambardzumian maria-hambardzumian commented Feb 11, 2026

https://jiraeu.epam.com/browse/EPMRPP-89449

Summary by CodeRabbit

  • New Features

    • Added a manual workflow to automatically synchronize GitHub Releases into repository documentation with configurable scope options (latest, last N releases, last month, or all releases).
  • Chores

    • Updated deployment authentication token configuration.

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

Walkthrough

This PR introduces a new GitHub Actions workflow and Node.js script to automatically synchronize GitHub releases into the repository as Markdown files, and updates the deployment workflow to use a custom GitHub token (GH_TOKEN instead of GITHUB_TOKEN).

Changes

Cohort / File(s) Summary
Deployment Configuration
.github/workflows/deploy.yml
Changed GitHub token source from GITHUB_TOKEN to GH_TOKEN for the gh-pages deployment step authentication.
Release Sync Workflow & Script
.github/workflows/sync-releases.yml, scripts/sync-releases.js
Added new GitHub Actions workflow to manually trigger release synchronization with configurable scope (latest, last-n, last-month, all), and accompanying Node.js script that fetches releases from GitHub API, applies filtering and transformation, generates Markdown files with front matter, and commits changes.

Sequence Diagram

sequenceDiagram
    actor User
    participant GHA as GitHub Actions<br/>(sync-releases)
    participant Script as sync-releases.js
    participant API as GitHub Releases API
    participant FS as File System
    participant Git as Git
    
    User->>GHA: Trigger workflow_dispatch
    GHA->>Script: Execute with SCOPE, GH_TOKEN
    Script->>API: Fetch releases (paginated)
    API-->>Script: Return releases data
    Script->>Script: Filter, sort, apply scope
    Script->>FS: Create docs/releases/
    Script->>FS: Check existing files
    Script->>Script: Transform release bodies
    Script->>FS: Write .md files with front matter
    Script->>Script: Detect changes
    Script->>Git: Configure user
    Script->>Git: Stage docs/releases/
    Script->>Git: Commit changes
    Script->>Git: Push to repository
    Git-->>User: Changes deployed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops with delight through GitHub's releases,
Syncing each page with elegant pieces,
From API calls to markdown's embrace,
The releases now have their rightful place!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: implementing functionality to load release versions from GitHub. This aligns with the primary purpose of adding a workflow and script to synchronize GitHub releases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/EPMRPP-89449-Load-release-versions-from-GitHub

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@maria-hambardzumian maria-hambardzumian marked this pull request as ready for review February 11, 2026 23:03
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In @.github/workflows/deploy.yml:
- Line 53: The workflows now rely on a custom secret named GH_TOKEN (referenced
as github_token: ${{ secrets.GH_TOKEN }}) so ensure that a Personal Access Token
is added to the repository Secrets as GH_TOKEN, that the PAT includes the
minimum required scopes (e.g., repo or contents: write) and that you have a
rotation/expiry plan for it; update any consumer workflows (deploy step and the
sync-releases usage of secrets.GH_TOKEN) to either validate the secret exists
(fail fast with a clear error) or fall back to GITHUB_TOKEN if appropriate, and
document the required secret name and scope in the repo README or CONTRIBUTING
guide.

In @.github/workflows/sync-releases.yml:
- Around line 29-33: The input "count" is not validated and can be non-numeric,
causing SYNC_SCOPE to become invalid; fix by (a) updating the input to enforce
numeric type and document format (change the input declaration for count to
type: number and adjust the description to "Number of releases to check
(positive integer, only used with \"last-n\")"), and/or (b) add a validation
step early in the workflow that reads the input "count" and checks it against a
regex like ^[1-9]\d*$ (fail the job with a clear error if it doesn't match)
before constructing SYNC_SCOPE so you never set SYNC_SCOPE to an invalid
"last-<value>".

In `@scripts/sync-releases.js`:
- Around line 165-176: The buildFileName function currently normalizes release
names by stripping stage suffixes via stripPrefix and
v.replace(...Final|RC|Beta|Alpha...), which causes collisions between stages
(e.g., "5.12 RC" and "5.12 Final" both become Version5.12.md) and also mislabels
BETA as RC in the /^BETA/i branch; update buildFileName to preserve the stage
qualifier in the generated filename (or add a clear code comment if intentional
to dedupe stages) and change the /^BETA/i branch to emit a Beta-specific
filename (e.g., Version{nums}Beta.md or preserve the original qualifier) rather
than Version{nums}RC.md; reference the buildFileName function and stripPrefix
call to locate and adjust the suffix-handling logic accordingly.
- Around line 46-48: The sidebar_position conflict happens because position = i
+ 1 is based on the current filtered index but existing files are skipped, so
their front-matter positions aren't updated and new inserts shift indices; to
fix, stop using the loop index and instead compute a deterministic,
non-colliding position: either (A) track createdCount and set position =
createdCount + 1 only for newly-created files (increment createdCount when you
actually write a file), or (B) sort releases by a stable key like
release.published_at and compute position from that order (e.g., position = 1 +
index in the published_at-sorted array) and then rewrite front-matter for all
files accordingly; update the code around buildSidebarLabel(name),
transformBody(...), and where position is assigned so position is derived from
createdCount or the published_at-sorted index instead of i.
- Around line 88-92: The cutoff calculation using
cutoff.setMonth(cutoff.getMonth() - 1) can roll forward on month-boundary dates
(e.g., Mar 31 -> Mar 3); replace that logic in the scope === 'last-month' branch
by computing now = new Date(), then create cutoff = new Date(now.getFullYear(),
now.getMonth() - 1, now.getDate()) and if cutoff.getMonth() !== (now.getMonth()
- 1 + 12) % 12, clamp to the previous month's last day with cutoff = new
Date(now.getFullYear(), now.getMonth(), 0); keep the existing
releases.filter((r) => new Date(r.published_at) >= cutoff) unchanged.
- Around line 110-114: The GH token check and warning are inside the while
(true) pagination loop causing the warning to print for every page; move the
block that checks process.env.GH_TOKEN and sets headers.Authorization (or emits
the console.warn) to execute once before entering the loop so headers is
prepared once and the warning is only emitted a single time; update the code
that currently references headers inside the loop to use the preconfigured
headers.
🧹 Nitpick comments (3)
scripts/sync-releases.js (1)

148-151: URL regex may capture trailing punctuation.

The pattern https?:\/\/[^\s)<>\]]+ will absorb trailing periods, commas, or colons that appear right after a URL (e.g., https://example.com. → link includes the .). This is a common issue in release notes where sentences end with a URL.

You could add common trailing punctuation to the exclusion set or strip it after matching:

Minimal fix — strip trailing punctuation from matched URL
   result = result.replace(
     /(?<!["\(])(?<!\]\()https?:\/\/[^\s)<>\]]+/g,
-    (url) => `[${extractLabel(url)}](${url})`,
+    (url) => {
+      const cleaned = url.replace(/[.,;:!?]+$/, '');
+      const trailing = url.slice(cleaned.length);
+      return `[${extractLabel(cleaned)}](${cleaned})${trailing}`;
+    },
   );
.github/workflows/sync-releases.yml (2)

70-77: Direct push to branch — consider if this aligns with your branch strategy.

This step pushes directly to whichever branch the workflow is dispatched on, bypassing pull request review. For automated doc generation this is common, but if your branch protection or team process requires review for all changes, consider creating a PR instead. This is just an operational note — no action required if direct push is intended.


44-46: Consider passing GH_TOKEN to checkout for consistency with git operations.

actions/checkout@v4 defaults to GITHUB_TOKEN for git credentials, and the git push at line 77 uses the same credentials. Since you already have GH_TOKEN available in the workflow (line 56), it's best practice to use it consistently for all git operations. This ensures the workflow works correctly if the repository has branch protection rules or other configurations that require a PAT instead of GITHUB_TOKEN.

Proposed fix
       - name: Checkout repository
         uses: actions/checkout@v4
+        with:
+          token: ${{ secrets.GH_TOKEN }}

uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_token: ${{ secrets.GH_TOKEN }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and display the deploy.yml file, particularly around line 53
fd -t f 'deploy.yml' -x cat -n {} | grep -A 5 -B 5 'github_token'

Repository: reportportal/docs

Length of output: 43


🏁 Script executed:

# Search for GH_TOKEN references across all workflow files
rg -n 'GH_TOKEN' --glob '*.yml' --glob '*.yaml'

Repository: reportportal/docs

Length of output: 43


🏁 Script executed:

# Check if there's any documentation about secret setup
fd -t f -e md | xargs rg -l 'GH_TOKEN|GITHUB_TOKEN' 2>/dev/null | head -20

Repository: reportportal/docs

Length of output: 43


🏁 Script executed:

# Look at the full deploy.yml file to understand context
find . -name 'deploy.yml' -path '*/.github/workflows/*' -exec wc -l {} \; -exec echo {} \; | paste - - | sort -n | head -5

Repository: reportportal/docs

Length of output: 139


🏁 Script executed:

# Find deploy.yml using git
git ls-files | grep -i deploy.yml

Repository: reportportal/docs

Length of output: 89


🏁 Script executed:

# Search for GH_TOKEN with a simpler rg command
rg 'GH_TOKEN' --type yaml

Repository: reportportal/docs

Length of output: 43


🏁 Script executed:

# Look for any workflow files that reference tokens
git ls-files '.github/workflows/' | head -20

Repository: reportportal/docs

Length of output: 183


🏁 Script executed:

# Read the deploy.yml file with line numbers
cat -n .github/workflows/deploy.yml

Repository: reportportal/docs

Length of output: 1978


🏁 Script executed:

# Search for GH_TOKEN in all file types
rg 'GH_TOKEN'

Repository: reportportal/docs

Length of output: 317


🏁 Script executed:

# Check README and documentation for secret setup info
rg 'GH_TOKEN|secret' README.md 2>/dev/null || echo "Not found in README"

Repository: reportportal/docs

Length of output: 80


🏁 Script executed:

# Check what tokens are used in other workflows
rg 'github_token|GITHUB_TOKEN|GH_TOKEN' .github/workflows/

Repository: reportportal/docs

Length of output: 217


🏁 Script executed:

# Check if there's any setup documentation or docs folder
find . -name '*.md' -type f | head -20

Repository: reportportal/docs

Length of output: 932


Ensure GH_TOKEN secret is provisioned and properly scoped.

The change from the built-in GITHUB_TOKEN to a custom GH_TOKEN secret (used in both deploy.yml and sync-releases.yml) requires manual configuration. If this secret is not set in the repository settings, the deploy step will fail.

Verify:

  1. The GH_TOKEN secret is configured in repository settings.
  2. The PAT has the minimum required scopes (typically repo or contents: write).
  3. There is a rotation/expiry plan for the PAT.
🤖 Prompt for AI Agents
In @.github/workflows/deploy.yml at line 53, The workflows now rely on a custom
secret named GH_TOKEN (referenced as github_token: ${{ secrets.GH_TOKEN }}) so
ensure that a Personal Access Token is added to the repository Secrets as
GH_TOKEN, that the PAT includes the minimum required scopes (e.g., repo or
contents: write) and that you have a rotation/expiry plan for it; update any
consumer workflows (deploy step and the sync-releases usage of secrets.GH_TOKEN)
to either validate the secret exists (fail fast with a clear error) or fall back
to GITHUB_TOKEN if appropriate, and document the required secret name and scope
in the repo README or CONTRIBUTING guide.

Comment on lines +29 to +33
count:
description: 'Number of releases to check (only used with "last-n")'
required: false
default: '5'
type: string
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

No validation that count is a positive integer.

The count input is a free-form string. If a user enters a non-numeric value (e.g., "abc"), SYNC_SCOPE becomes last-abc, which silently falls back to "latest" in the script. Consider adding a validation step, or at minimum, document the expected format in the description.

🤖 Prompt for AI Agents
In @.github/workflows/sync-releases.yml around lines 29 - 33, The input "count"
is not validated and can be non-numeric, causing SYNC_SCOPE to become invalid;
fix by (a) updating the input to enforce numeric type and document format
(change the input declaration for count to type: number and adjust the
description to "Number of releases to check (positive integer, only used with
\"last-n\")"), and/or (b) add a validation step early in the workflow that reads
the input "count" and checks it against a regex like ^[1-9]\d*$ (fail the job
with a clear error if it doesn't match) before constructing SYNC_SCOPE so you
never set SYNC_SCOPE to an invalid "last-<value>".

Comment on lines +46 to +48
const sidebarLabel = buildSidebarLabel(name);
const body = transformBody(release.body || '');
const position = i + 1;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

sidebar_position will conflict with previously created files on subsequent runs.

position is derived from the index in the current filtered array (line 48), but existing files are skipped without updating their front matter. When new releases are published and the script is re-run, the new release shifts all indices, yet the already-written files retain their old sidebar_position values. This produces duplicates and breaks the intended Docusaurus sidebar ordering.

For example: Run 1 creates releases at positions 1, 2, 3. A new release is added. Run 2 assigns position 1 to the new release, but the old file at position 1 is never updated.

Consider one of:

  • Rewriting all files on every run (remove the skip-existing logic).
  • Computing position only from the count of newly created files.
  • Using published_at as a stable sort key (e.g., a timestamp-based position) so that each release always gets a deterministic, non-colliding position.
🤖 Prompt for AI Agents
In `@scripts/sync-releases.js` around lines 46 - 48, The sidebar_position conflict
happens because position = i + 1 is based on the current filtered index but
existing files are skipped, so their front-matter positions aren't updated and
new inserts shift indices; to fix, stop using the loop index and instead compute
a deterministic, non-colliding position: either (A) track createdCount and set
position = createdCount + 1 only for newly-created files (increment createdCount
when you actually write a file), or (B) sort releases by a stable key like
release.published_at and compute position from that order (e.g., position = 1 +
index in the published_at-sorted array) and then rewrite front-matter for all
files accordingly; update the code around buildSidebarLabel(name),
transformBody(...), and where position is assigned so position is derived from
createdCount or the published_at-sorted index instead of i.

Comment on lines +88 to +92
if (scope === 'last-month') {
const cutoff = new Date();
cutoff.setMonth(cutoff.getMonth() - 1);
return releases.filter((r) => new Date(r.published_at) >= cutoff);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

last-month cutoff may be inaccurate on month-boundary dates.

Date.setMonth(getMonth() - 1) on dates like March 31 rolls forward because February doesn't have 31 days (resulting in ~March 3 instead of ~February 28). This could silently include or exclude releases near the boundary.

A safer approach is to also clamp the day:

Proposed fix
   if (scope === 'last-month') {
     const cutoff = new Date();
-    cutoff.setMonth(cutoff.getMonth() - 1);
+    const targetMonth = cutoff.getMonth() - 1;
+    cutoff.setDate(1);          // avoid day overflow
+    cutoff.setMonth(targetMonth);
     return releases.filter((r) => new Date(r.published_at) >= cutoff);
   }
🤖 Prompt for AI Agents
In `@scripts/sync-releases.js` around lines 88 - 92, The cutoff calculation using
cutoff.setMonth(cutoff.getMonth() - 1) can roll forward on month-boundary dates
(e.g., Mar 31 -> Mar 3); replace that logic in the scope === 'last-month' branch
by computing now = new Date(), then create cutoff = new Date(now.getFullYear(),
now.getMonth() - 1, now.getDate()) and if cutoff.getMonth() !== (now.getMonth()
- 1 + 12) % 12, clamp to the previous month's last day with cutoff = new
Date(now.getFullYear(), now.getMonth(), 0); keep the existing
releases.filter((r) => new Date(r.published_at) >= cutoff) unchanged.

Comment on lines +110 to +114
if (process.env.GH_TOKEN) {
headers.Authorization = `Bearer ${process.env.GH_TOKEN}`;
} else {
console.warn('GH_TOKEN not set - using unauthenticated API (60 req/hr limit)');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

GH_TOKEN warning is emitted on every paginated request instead of once.

The token check is inside the while (true) loop, so the warning prints on every page fetch. Move it before the loop.

Proposed fix
 async function fetchAllReleases() {
   const all = [];
   let page = 1;
+  const headers = {
+    Accept: 'application/vnd.github+json',
+    'X-GitHub-Api-Version': '2022-11-28',
+    'User-Agent': 'reportportal-docs-sync',
+  };
+
+  if (process.env.GH_TOKEN) {
+    headers.Authorization = `Bearer ${process.env.GH_TOKEN}`;
+  } else {
+    console.warn('GH_TOKEN not set - using unauthenticated API (60 req/hr limit)');
+  }
 
   while (true) {
     const url = `${RELEASES_API_URL}?per_page=${RELEASES_PER_PAGE}&page=${page}`;
-    const headers = {
-      Accept: 'application/vnd.github+json',
-      'X-GitHub-Api-Version': '2022-11-28',
-      'User-Agent': 'reportportal-docs-sync',
-    };
-
-    if (process.env.GH_TOKEN) {
-      headers.Authorization = `Bearer ${process.env.GH_TOKEN}`;
-    } else {
-      console.warn('GH_TOKEN not set - using unauthenticated API (60 req/hr limit)');
-    }
 
     console.log(`Fetching releases page ${page}...`);
     const res = await fetch(url, { headers });
🤖 Prompt for AI Agents
In `@scripts/sync-releases.js` around lines 110 - 114, The GH token check and
warning are inside the while (true) pagination loop causing the warning to print
for every page; move the block that checks process.env.GH_TOKEN and sets
headers.Authorization (or emits the console.warn) to execute once before
entering the loop so headers is prepared once and the warning is only emitted a
single time; update the code that currently references headers inside the loop
to use the preconfigured headers.

Comment on lines +165 to +176
function buildFileName(name) {
let v = stripPrefix(name);

if (/^BETA/i.test(v)) {
const nums = v.match(/[\d.]+/);
return nums ? `Version${nums[0]}RC.md` : `Version${v.replace(/\s+/g, '')}.md`;
}

v = v.replace(/\s+(Final|RC|Beta|Alpha)$/i, '').trim();

return `Version${v}.md`;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

File-name collisions between release stages, and "BETA" mislabeled as "RC".

Two issues:

  1. Collision: Line 173 strips Final, RC, Beta, and Alpha suffixes, so releases like "5.12 RC" and "5.12 Final" both produce Version5.12.md. The second one is silently dropped. If this is intentional (keep only the latest stage), it deserves a code comment explaining the design choice. If not, the suffix should be preserved in the filename.

  2. Semantic mismatch: Lines 168-170 treat BETA names as RC (filename VersionX.YRC.md). Beta and RC are distinct stages. Consider using VersionX.YBeta.md instead, or preserving the original qualifier.

🤖 Prompt for AI Agents
In `@scripts/sync-releases.js` around lines 165 - 176, The buildFileName function
currently normalizes release names by stripping stage suffixes via stripPrefix
and v.replace(...Final|RC|Beta|Alpha...), which causes collisions between stages
(e.g., "5.12 RC" and "5.12 Final" both become Version5.12.md) and also mislabels
BETA as RC in the /^BETA/i branch; update buildFileName to preserve the stage
qualifier in the generated filename (or add a clear code comment if intentional
to dedupe stages) and change the /^BETA/i branch to emit a Beta-specific
filename (e.g., Version{nums}Beta.md or preserve the original qualifier) rather
than Version{nums}RC.md; reference the buildFileName function and stripPrefix
call to locate and adjust the suffix-handling logic accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant