EPMRPP-89449 || Load release versions from GitHub#1068
EPMRPP-89449 || Load release versions from GitHub#1068maria-hambardzumian wants to merge 1 commit intodevelopfrom
Conversation
WalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 passingGH_TOKENto checkout for consistency with git operations.
actions/checkout@v4defaults toGITHUB_TOKENfor git credentials, and thegit pushat line 77 uses the same credentials. Since you already haveGH_TOKENavailable 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 ofGITHUB_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 }} |
There was a problem hiding this comment.
🧩 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 -20Repository: 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 -5Repository: reportportal/docs
Length of output: 139
🏁 Script executed:
# Find deploy.yml using git
git ls-files | grep -i deploy.ymlRepository: reportportal/docs
Length of output: 89
🏁 Script executed:
# Search for GH_TOKEN with a simpler rg command
rg 'GH_TOKEN' --type yamlRepository: reportportal/docs
Length of output: 43
🏁 Script executed:
# Look for any workflow files that reference tokens
git ls-files '.github/workflows/' | head -20Repository: reportportal/docs
Length of output: 183
🏁 Script executed:
# Read the deploy.yml file with line numbers
cat -n .github/workflows/deploy.ymlRepository: 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 -20Repository: 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:
- The
GH_TOKENsecret is configured in repository settings. - The PAT has the minimum required scopes (typically
repoorcontents: write). - 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.
| count: | ||
| description: 'Number of releases to check (only used with "last-n")' | ||
| required: false | ||
| default: '5' | ||
| type: string |
There was a problem hiding this comment.
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>".
| const sidebarLabel = buildSidebarLabel(name); | ||
| const body = transformBody(release.body || ''); | ||
| const position = i + 1; |
There was a problem hiding this comment.
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_atas 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.
| if (scope === 'last-month') { | ||
| const cutoff = new Date(); | ||
| cutoff.setMonth(cutoff.getMonth() - 1); | ||
| return releases.filter((r) => new Date(r.published_at) >= cutoff); | ||
| } |
There was a problem hiding this comment.
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.
| 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)'); | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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`; | ||
| } |
There was a problem hiding this comment.
File-name collisions between release stages, and "BETA" mislabeled as "RC".
Two issues:
-
Collision: Line 173 strips
Final,RC,Beta, andAlphasuffixes, so releases like "5.12 RC" and "5.12 Final" both produceVersion5.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. -
Semantic mismatch: Lines 168-170 treat
BETAnames asRC(filenameVersionX.YRC.md). Beta and RC are distinct stages. Consider usingVersionX.YBeta.mdinstead, 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.
https://jiraeu.epam.com/browse/EPMRPP-89449
Summary by CodeRabbit
New Features
Chores