From 9fae863720fed6bf4a65c911722c41cf9e23bbdf Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Tue, 18 Nov 2025 13:50:15 +0100 Subject: [PATCH 01/33] feat: Add wiki update workflow and script - Add GitHub Actions workflow to update wiki pages on pushes to main and release branches - Add Python script to generate wiki content documenting all workspaces - Configure workflow to check upstream repository for pending PRs - Remove unnecessary fetch-depth setting --- .github/scripts/generate-wiki-page.py | 380 ++++++++++++++++++++++++++ .github/workflows/update-wiki.yml | 71 +++++ 2 files changed, 451 insertions(+) create mode 100755 .github/scripts/generate-wiki-page.py create mode 100644 .github/workflows/update-wiki.yml diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py new file mode 100755 index 00000000..35839e36 --- /dev/null +++ b/.github/scripts/generate-wiki-page.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 +""" +Generate a wiki page documenting all workspaces in the current branch. + +This script: +1. Scans the workspaces/ directory for all workspace folders +2. Extracts metadata from source.json and plugins-list.yaml +3. Checks for pending PRs that modify each workspace +4. Generates a formatted Markdown page with all workspace details +""" + +import os +import sys +import json +import yaml +import subprocess +from pathlib import Path +from typing import Dict, List, Optional, Tuple +from datetime import datetime + + +def run_command(cmd: List[str], check: bool = True) -> Tuple[int, str, str]: + """Run a shell command and return exit code, stdout, stderr.""" + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=False + ) + if check and result.returncode != 0: + print(f"Command failed: {' '.join(cmd)}", file=sys.stderr) + print(f"Exit code: {result.returncode}", file=sys.stderr) + print(f"Stderr: {result.stderr}", file=sys.stderr) + return result.returncode, result.stdout.strip(), result.stderr.strip() + + +def get_workspace_list(workspaces_dir: Path) -> List[str]: + """Get list of all workspace directories.""" + if not workspaces_dir.exists(): + print(f"Workspaces directory not found: {workspaces_dir}", file=sys.stderr) + return [] + + workspaces = [] + for item in sorted(workspaces_dir.iterdir()): + if item.is_dir() and not item.name.startswith('.'): + workspaces.append(item.name) + + return workspaces + + +def parse_source_json(workspace_path: Path) -> Optional[Dict]: + """Parse source.json file from a workspace.""" + source_file = workspace_path / "source.json" + if not source_file.exists(): + return None + + try: + with open(source_file, 'r') as f: + return json.load(f) + except (json.JSONDecodeError, IOError) as e: + print(f"Error reading {source_file}: {e}", file=sys.stderr) + return None + + +def parse_plugins_list(workspace_path: Path) -> List[str]: + """Parse plugins-list.yaml file from a workspace.""" + plugins_file = workspace_path / "plugins-list.yaml" + if not plugins_file.exists(): + return [] + + try: + with open(plugins_file, 'r') as f: + content = f.read().strip() + if not content: + return [] + + # Parse YAML - it's a simple list of plugin paths + data = yaml.safe_load(content) + if isinstance(data, dict): + # Keys are plugin paths + return list(data.keys()) + elif isinstance(data, list): + return data + else: + return [] + except (yaml.YAMLError, IOError) as e: + print(f"Error reading {plugins_file}: {e}", file=sys.stderr) + return [] + + +def get_commit_details(repo_url: str, commit_sha: str) -> Tuple[str, str, str]: + """ + Get commit details including short SHA, commit message, and date. + Returns (short_sha, message, date) + """ + # Extract owner/repo from URL + if not repo_url.startswith("https://github.com/"): + return commit_sha[:7], "N/A", "N/A" + + repo_path = repo_url.replace("https://github.com/", "").rstrip('/') + + # Try to get commit details using gh CLI + cmd = [ + "gh", "api", + f"/repos/{repo_path}/commits/{commit_sha}", + "--jq", ".sha, .commit.message, .commit.author.date" + ] + + exit_code, stdout, stderr = run_command(cmd, check=False) + + if exit_code == 0 and stdout: + lines = stdout.split('\n') + if len(lines) >= 3: + full_sha = lines[0] + message = lines[1].split('\n')[0] # First line of commit message + date = lines[2] + + # Format date + try: + dt = datetime.fromisoformat(date.replace('Z', '+00:00')) + formatted_date = dt.strftime('%Y-%m-%d %H:%M UTC') + except: + formatted_date = date + + return full_sha[:7], message, formatted_date + + # Fallback + return commit_sha[:7], "N/A", "N/A" + + +def check_pending_prs(workspace_name: str, repo_name: str) -> Tuple[bool, List[str]]: + """ + Check if there are any open PRs that modify this workspace. + Returns (has_pending, [pr_numbers]) + """ + workspace_path = f"workspaces/{workspace_name}" + + # Use gh CLI to search for open PRs + cmd = [ + "gh", "pr", "list", + "--repo", repo_name, + "--state", "open", + "--json", "number,files", + "--jq", f'.[] | select(.files[].path | startswith("{workspace_path}")) | .number' + ] + + exit_code, stdout, stderr = run_command(cmd, check=False) + + if exit_code == 0 and stdout: + pr_numbers = [line.strip() for line in stdout.split('\n') if line.strip()] + return len(pr_numbers) > 0, pr_numbers + + return False, [] + + +def get_backstage_version(workspace_path: Path) -> Optional[str]: + """Get Backstage version from backstage.json or source.json.""" + # Check backstage.json first + backstage_file = workspace_path / "backstage.json" + if backstage_file.exists(): + try: + with open(backstage_file, 'r') as f: + data = json.load(f) + if 'version' in data: + return data['version'] + except (json.JSONDecodeError, IOError): + pass + + # Fall back to source.json + source_data = parse_source_json(workspace_path) + if source_data and 'repo-backstage-version' in source_data: + return source_data['repo-backstage-version'] + + return None + + +def count_additional_files(workspace_path: Path) -> Dict[str, int]: + """Count additional configuration files in the workspace.""" + counts = { + 'metadata': 0, + 'plugins': 0, + 'patches': 0, + 'tests': 0 + } + + for key in counts.keys(): + dir_path = workspace_path / key + if dir_path.exists() and dir_path.is_dir(): + # Count all files recursively + counts[key] = sum(1 for _ in dir_path.rglob('*') if _.is_file()) + + return counts + + +def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: + """Generate the Markdown content for the wiki page.""" + md = [] + + # Header + md.append(f"# Workspace Status: `{branch_name}`") + md.append("") + md.append(f"**Last Updated:** {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}") + md.append("") + md.append(f"**Total Workspaces:** {len(workspaces_data)}") + md.append("") + md.append("---") + md.append("") + + # Table of Contents + md.append("## Table of Contents") + md.append("") + for ws in workspaces_data: + anchor = ws['name'].lower().replace('_', '-').replace(' ', '-') + md.append(f"- [{ws['name']}](#{anchor})") + md.append("") + md.append("---") + md.append("") + + # Workspace Details + for ws in workspaces_data: + md.append(f"## {ws['name']}") + md.append("") + + # Source Repository + if ws['repo_url']: + repo_name = ws['repo_url'].replace('https://github.com/', '') + md.append(f"**Source Repository:** [{repo_name}]({ws['repo_url']})") + else: + md.append("**Source Repository:** N/A") + + # Commit Information + if ws['commit_sha']: + commit_link = f"{ws['repo_url']}/commit/{ws['commit_sha']}" + md.append(f"**Pinned Commit:** [{ws['commit_short']}]({commit_link})") + if ws['commit_message'] != "N/A": + md.append(f"**Commit Message:** {ws['commit_message']}") + if ws['commit_date'] != "N/A": + md.append(f"**Commit Date:** {ws['commit_date']}") + else: + md.append("**Pinned Commit:** N/A") + + # Backstage Version + if ws['backstage_version']: + md.append(f"**Backstage Version:** `{ws['backstage_version']}`") + + # Workspace Type + workspace_type = "Monorepo (workspace-based)" if not ws['repo_flat'] else "Flat (root-level plugins)" + md.append(f"**Repository Structure:** {workspace_type}") + + md.append("") + + # Plugins + md.append(f"**Plugins ({len(ws['plugins'])}):**") + md.append("") + if ws['plugins']: + for plugin in ws['plugins']: + md.append(f"- `{plugin}`") + else: + md.append("- *No plugins listed*") + md.append("") + + # Additional Files + additional = ws['additional_files'] + if any(additional.values()): + md.append("**Additional Configuration:**") + md.append("") + if additional['metadata'] > 0: + md.append(f"- Metadata files: {additional['metadata']}") + if additional['plugins'] > 0: + md.append(f"- Plugin overlays: {additional['plugins']}") + if additional['patches'] > 0: + md.append(f"- Patches: {additional['patches']}") + if additional['tests'] > 0: + md.append(f"- Test files: {additional['tests']}") + md.append("") + + # Pending PRs + if ws['has_pending_prs']: + pr_links = [] + for pr_num in ws['pr_numbers']: + pr_url = f"https://github.com/{os.getenv('REPO_NAME')}/pull/{pr_num}" + pr_links.append(f"[#{pr_num}]({pr_url})") + + md.append(f"**Pending Updates:** ⚠️ Yes - {', '.join(pr_links)}") + else: + md.append("**Pending Updates:** ✅ No") + + md.append("") + md.append("---") + md.append("") + + return '\n'.join(md) + + +def main(): + """Main entry point.""" + # Get environment variables + branch_name = os.getenv('BRANCH_NAME', 'main') + repo_name = os.getenv('REPO_NAME', 'unknown/unknown') + + print(f"Generating wiki page for branch: {branch_name}") + print(f"Repository: {repo_name}") + + # Get workspace directory + workspaces_dir = Path('workspaces') + + # Get list of workspaces + workspace_names = get_workspace_list(workspaces_dir) + print(f"Found {len(workspace_names)} workspaces") + + # Collect data for each workspace + workspaces_data = [] + + for ws_name in workspace_names: + print(f"Processing workspace: {ws_name}") + ws_path = workspaces_dir / ws_name + + # Parse source.json + source_data = parse_source_json(ws_path) + + # Parse plugins-list.yaml + plugins = parse_plugins_list(ws_path) + + # Get commit details + commit_sha = None + commit_short = None + commit_message = "N/A" + commit_date = "N/A" + repo_url = None + repo_flat = False + + if source_data: + repo_url = source_data.get('repo', '') + commit_sha = source_data.get('repo-ref', '') + repo_flat = source_data.get('repo-flat', False) + + if repo_url and commit_sha: + commit_short, commit_message, commit_date = get_commit_details(repo_url, commit_sha) + + # Get Backstage version + backstage_version = get_backstage_version(ws_path) + + # Count additional files + additional_files = count_additional_files(ws_path) + + # Check for pending PRs + has_pending_prs, pr_numbers = check_pending_prs(ws_name, repo_name) + + workspaces_data.append({ + 'name': ws_name, + 'repo_url': repo_url, + 'commit_sha': commit_sha, + 'commit_short': commit_short, + 'commit_message': commit_message, + 'commit_date': commit_date, + 'repo_flat': repo_flat, + 'backstage_version': backstage_version, + 'plugins': plugins, + 'additional_files': additional_files, + 'has_pending_prs': has_pending_prs, + 'pr_numbers': pr_numbers + }) + + # Generate Markdown + print("Generating Markdown content...") + markdown_content = generate_markdown(branch_name, workspaces_data) + + # Write to file + output_file = f"{branch_name}.md" + with open(output_file, 'w') as f: + f.write(markdown_content) + + print(f"Wiki page generated: {output_file}") + print(f"Total workspaces documented: {len(workspaces_data)}") + + +if __name__ == '__main__': + main() + + diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml new file mode 100644 index 00000000..427dda6b --- /dev/null +++ b/.github/workflows/update-wiki.yml @@ -0,0 +1,71 @@ +name: Update Wiki Pages + +on: + push: + branches: + - main + - 'release-*' + +permissions: + contents: write + pull-requests: read + +jobs: + update-wiki: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + pip install pyyaml requests + + - name: Get branch name + id: branch + run: | + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + echo "name=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "Branch: ${BRANCH_NAME}" + + - name: Generate wiki content + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: ${{ steps.branch.outputs.name }} + REPO_NAME: redhat-developer/rhdh-plugin-export-overlays + run: | + python .github/scripts/generate-wiki-page.py + + - name: Checkout wiki repository + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }}.wiki + path: wiki + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Copy generated wiki page to wiki repo + run: | + cp "${{ steps.branch.outputs.name }}.md" wiki/ + + - name: Commit and push wiki changes + working-directory: wiki + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Check if there are changes + if git diff --quiet && git diff --cached --quiet; then + echo "No changes to commit" + exit 0 + fi + + git add "${{ steps.branch.outputs.name }}.md" + git commit -m "Update ${{ steps.branch.outputs.name }} workspace status [automated]" + git push + + From 45eaa9b98c31f8370a19404ef1484321032615b4 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Tue, 18 Nov 2025 15:03:56 +0100 Subject: [PATCH 02/33] test: Modify workflow to push wiki to test branch instead of wiki repo - Create wiki-test branch to store generated wiki pages - Push to test branch for demonstration purposes - Avoid wiki repository creation issues --- .github/workflows/update-wiki.yml | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index 427dda6b..441c383a 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -41,31 +41,23 @@ jobs: run: | python .github/scripts/generate-wiki-page.py - - name: Checkout wiki repository - uses: actions/checkout@v4 - with: - repository: ${{ github.repository }}.wiki - path: wiki - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Copy generated wiki page to wiki repo - run: | - cp "${{ steps.branch.outputs.name }}.md" wiki/ - - - name: Commit and push wiki changes - working-directory: wiki + - name: Create test wiki branch run: | + # Create a test branch to store wiki pages for demonstration + git checkout -b wiki-test 2>/dev/null || git checkout wiki-test + mkdir -p wiki-pages + cp "${{ steps.branch.outputs.name }}.md" wiki-pages/ + git add wiki-pages/ git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - + # Check if there are changes - if git diff --quiet && git diff --cached --quiet; then + if git diff --quiet --cached; then echo "No changes to commit" exit 0 fi - - git add "${{ steps.branch.outputs.name }}.md" + git commit -m "Update ${{ steps.branch.outputs.name }} workspace status [automated]" - git push + git push origin wiki-test --force From bb254c94cf3cb343a0ffa1e9daf9a99bf69e30ee Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Tue, 18 Nov 2025 15:07:24 +0100 Subject: [PATCH 03/33] fix: Sanitize branch names for wiki filenames - Replace slashes with dashes in branch names to avoid file path issues - Fixes error when creating wiki pages for feature branches --- .github/workflows/update-wiki.yml | 34 ++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index 441c383a..93661578 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -30,8 +30,10 @@ jobs: id: branch run: | BRANCH_NAME="${GITHUB_REF#refs/heads/}" - echo "name=${BRANCH_NAME}" >> $GITHUB_OUTPUT - echo "Branch: ${BRANCH_NAME}" + # Replace slashes with dashes for filename compatibility + SANITIZED_BRANCH_NAME="${BRANCH_NAME//\//-}" + echo "name=${SANITIZED_BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "Branch: ${BRANCH_NAME} (sanitized: ${SANITIZED_BRANCH_NAME})" - name: Generate wiki content env: @@ -41,23 +43,31 @@ jobs: run: | python .github/scripts/generate-wiki-page.py - - name: Create test wiki branch + - name: Checkout wiki repository + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }}.wiki + path: wiki + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Copy generated wiki page to wiki repo + run: | + cp "${{ steps.branch.outputs.name }}.md" wiki/ + + - name: Commit and push wiki changes + working-directory: wiki run: | - # Create a test branch to store wiki pages for demonstration - git checkout -b wiki-test 2>/dev/null || git checkout wiki-test - mkdir -p wiki-pages - cp "${{ steps.branch.outputs.name }}.md" wiki-pages/ - git add wiki-pages/ git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - + # Check if there are changes - if git diff --quiet --cached; then + if git diff --quiet && git diff --cached --quiet; then echo "No changes to commit" exit 0 fi - + + git add "${{ steps.branch.outputs.name }}.md" git commit -m "Update ${{ steps.branch.outputs.name }} workspace status [automated]" - git push origin wiki-test --force + git push From c56cddbc5237264ca57d71b598e6478c7bab390f Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Tue, 18 Nov 2025 15:39:25 +0100 Subject: [PATCH 04/33] fix: Sanitize branch names for wiki filenames - Replace '/' with '-' in branch names when creating wiki pages - Update workflow to use sanitized branch name for file operations - Temporarily enable workflow on feature/RHIDP-9450 for testing --- .github/scripts/generate-wiki-page.py | 5 +++-- .github/workflows/update-wiki.yml | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 35839e36..fa4c9b4a 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -365,8 +365,9 @@ def main(): print("Generating Markdown content...") markdown_content = generate_markdown(branch_name, workspaces_data) - # Write to file - output_file = f"{branch_name}.md" + # Write to file (sanitize branch name for filename) + safe_branch_name = branch_name.replace('/', '-') + output_file = f"{safe_branch_name}.md" with open(output_file, 'w') as f: f.write(markdown_content) diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index 93661578..9bc571fb 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -5,6 +5,7 @@ on: branches: - main - 'release-*' + - 'feature/RHIDP-9450' permissions: contents: write @@ -30,10 +31,11 @@ jobs: id: branch run: | BRANCH_NAME="${GITHUB_REF#refs/heads/}" - # Replace slashes with dashes for filename compatibility - SANITIZED_BRANCH_NAME="${BRANCH_NAME//\//-}" - echo "name=${SANITIZED_BRANCH_NAME}" >> $GITHUB_OUTPUT - echo "Branch: ${BRANCH_NAME} (sanitized: ${SANITIZED_BRANCH_NAME})" + # Sanitize branch name for filename (replace / with -) + SAFE_BRANCH_NAME="${BRANCH_NAME//\//-}" + echo "name=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "safe_name=${SAFE_BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "Branch: ${BRANCH_NAME}" - name: Generate wiki content env: @@ -52,7 +54,7 @@ jobs: - name: Copy generated wiki page to wiki repo run: | - cp "${{ steps.branch.outputs.name }}.md" wiki/ + cp "${{ steps.branch.outputs.safe_name }}.md" wiki/ - name: Commit and push wiki changes working-directory: wiki @@ -66,7 +68,7 @@ jobs: exit 0 fi - git add "${{ steps.branch.outputs.name }}.md" + git add "${{ steps.branch.outputs.safe_name }}.md" git commit -m "Update ${{ steps.branch.outputs.name }} workspace status [automated]" git push From af09cc8e4139f9654c3457bd57dd0a2c7ebb2b5f Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Tue, 18 Nov 2025 16:16:00 +0100 Subject: [PATCH 05/33] debug: Add logging to troubleshoot wiki generation and copy steps --- .github/workflows/update-wiki.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index 9bc571fb..4cf5ce63 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -43,7 +43,10 @@ jobs: BRANCH_NAME: ${{ steps.branch.outputs.name }} REPO_NAME: redhat-developer/rhdh-plugin-export-overlays run: | + echo "Running wiki generation script..." python .github/scripts/generate-wiki-page.py + echo "Files after script execution:" + ls -la *.md || echo "No .md files found" - name: Checkout wiki repository uses: actions/checkout@v4 @@ -54,7 +57,11 @@ jobs: - name: Copy generated wiki page to wiki repo run: | + echo "Looking for file: ${{ steps.branch.outputs.safe_name }}.md" + ls -la "${{ steps.branch.outputs.safe_name }}.md" || echo "File not found!" cp "${{ steps.branch.outputs.safe_name }}.md" wiki/ + echo "Files in wiki directory:" + ls -la wiki/ - name: Commit and push wiki changes working-directory: wiki From d5f901b9b25b4a19148e2bcad2db948149eb545d Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Tue, 18 Nov 2025 16:25:41 +0100 Subject: [PATCH 06/33] fix: Stage file before checking for changes in wiki commit The issue was that git diff was checking for changes before staging the file. New untracked files don't show up in git diff, so the script would exit early. Now we stage the file first, then check git diff --cached for staged changes. --- .github/workflows/update-wiki.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index 4cf5ce63..bf27210d 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -69,13 +69,15 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - # Check if there are changes - if git diff --quiet && git diff --cached --quiet; then + # Add the file first + git add "${{ steps.branch.outputs.safe_name }}.md" + + # Check if there are changes (after staging) + if git diff --cached --quiet; then echo "No changes to commit" exit 0 fi - git add "${{ steps.branch.outputs.safe_name }}.md" git commit -m "Update ${{ steps.branch.outputs.name }} workspace status [automated]" git push From 2a8bd942029c4a198d841a295f0a8ebc2dab429d Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Tue, 18 Nov 2025 17:06:34 +0100 Subject: [PATCH 07/33] chore: Remove debug logging and test branch trigger - Remove debug echo statements from workflow - Remove temporary feature/RHIDP-9450 branch trigger - Clean up workflow for production use --- .github/workflows/update-wiki.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index bf27210d..ee3a9976 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -5,7 +5,6 @@ on: branches: - main - 'release-*' - - 'feature/RHIDP-9450' permissions: contents: write @@ -43,10 +42,7 @@ jobs: BRANCH_NAME: ${{ steps.branch.outputs.name }} REPO_NAME: redhat-developer/rhdh-plugin-export-overlays run: | - echo "Running wiki generation script..." python .github/scripts/generate-wiki-page.py - echo "Files after script execution:" - ls -la *.md || echo "No .md files found" - name: Checkout wiki repository uses: actions/checkout@v4 @@ -57,11 +53,7 @@ jobs: - name: Copy generated wiki page to wiki repo run: | - echo "Looking for file: ${{ steps.branch.outputs.safe_name }}.md" - ls -la "${{ steps.branch.outputs.safe_name }}.md" || echo "File not found!" cp "${{ steps.branch.outputs.safe_name }}.md" wiki/ - echo "Files in wiki directory:" - ls -la wiki/ - name: Commit and push wiki changes working-directory: wiki From dca4dbc9c5524a946d9102f9d6219f033db5ce35 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 09:15:17 +0100 Subject: [PATCH 08/33] feat: Convert workspace list to table format - Replace individual workspace sections with a compact table - Table shows: Workspace, Source Repo, Pinned Commit, Backstage Version, Plugins count, Pending Updates - Keep detailed information in separate section below table - Fix datetime deprecation warning by using timezone.utc --- .github/scripts/generate-wiki-page.py | 113 ++++++++++++++------------ 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index fa4c9b4a..2d6c5431 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -16,7 +16,7 @@ import subprocess from pathlib import Path from typing import Dict, List, Optional, Tuple -from datetime import datetime +from datetime import datetime, timezone def run_command(cmd: List[str], check: bool = True) -> Tuple[int, str, str]: @@ -199,66 +199,87 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Header md.append(f"# Workspace Status: `{branch_name}`") md.append("") - md.append(f"**Last Updated:** {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}") + md.append(f"**Last Updated:** {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')}") md.append("") md.append(f"**Total Workspaces:** {len(workspaces_data)}") md.append("") md.append("---") md.append("") - # Table of Contents - md.append("## Table of Contents") + # Workspace Table + md.append("## Workspace Overview") md.append("") + md.append("| Workspace | Source Repository | Pinned Commit | Backstage Version | Plugins | Pending Updates |") + md.append("|-----------|------------------|---------------|------------------|---------|----------------|") + for ws in workspaces_data: - anchor = ws['name'].lower().replace('_', '-').replace(' ', '-') - md.append(f"- [{ws['name']}](#{anchor})") - md.append("") - md.append("---") - md.append("") - - # Workspace Details - for ws in workspaces_data: - md.append(f"## {ws['name']}") - md.append("") - + # Workspace name + workspace_name = ws['name'] + # Source Repository if ws['repo_url']: repo_name = ws['repo_url'].replace('https://github.com/', '') - md.append(f"**Source Repository:** [{repo_name}]({ws['repo_url']})") + source_repo = f"[{repo_name}]({ws['repo_url']})" else: - md.append("**Source Repository:** N/A") - - # Commit Information + source_repo = "N/A" + + # Pinned Commit if ws['commit_sha']: commit_link = f"{ws['repo_url']}/commit/{ws['commit_sha']}" - md.append(f"**Pinned Commit:** [{ws['commit_short']}]({commit_link})") - if ws['commit_message'] != "N/A": - md.append(f"**Commit Message:** {ws['commit_message']}") - if ws['commit_date'] != "N/A": - md.append(f"**Commit Date:** {ws['commit_date']}") + pinned_commit = f"[{ws['commit_short']}]({commit_link})" else: - md.append("**Pinned Commit:** N/A") - + pinned_commit = "N/A" + # Backstage Version - if ws['backstage_version']: - md.append(f"**Backstage Version:** `{ws['backstage_version']}`") - - # Workspace Type + backstage_version = f"`{ws['backstage_version']}`" if ws['backstage_version'] else "N/A" + + # Plugins Count + plugins_count = f"{len(ws['plugins'])} plugins" + + # Pending PRs + if ws['has_pending_prs']: + pr_links = [] + for pr_num in ws['pr_numbers']: + pr_url = f"https://github.com/{os.getenv('REPO_NAME')}/pull/{pr_num}" + pr_links.append(f"[#{pr_num}]({pr_url})") + pending_updates = f"⚠️ {', '.join(pr_links)}" + else: + pending_updates = "✅ No" + + # Add table row + md.append(f"| {workspace_name} | {source_repo} | {pinned_commit} | {backstage_version} | {plugins_count} | {pending_updates} |") + + md.append("") + md.append("---") + md.append("") + + # Detailed Workspace Information + md.append("## Detailed Workspace Information") + md.append("") + for ws in workspaces_data: + md.append(f"### {ws['name']}") + md.append("") + + # Commit Details (if available) + if ws['commit_sha'] and ws['commit_message'] != "N/A": + md.append(f"**Latest Commit:** {ws['commit_message']}") + if ws['commit_date'] != "N/A": + md.append(f"**Commit Date:** {ws['commit_date']}") + md.append("") + + # Repository Structure workspace_type = "Monorepo (workspace-based)" if not ws['repo_flat'] else "Flat (root-level plugins)" md.append(f"**Repository Structure:** {workspace_type}") - - md.append("") - - # Plugins - md.append(f"**Plugins ({len(ws['plugins'])}):**") md.append("") + + # Plugin List if ws['plugins']: + md.append("**Plugins:**") + md.append("") for plugin in ws['plugins']: md.append(f"- `{plugin}`") - else: - md.append("- *No plugins listed*") - md.append("") - + md.append("") + # Additional Files additional = ws['additional_files'] if any(additional.values()): @@ -273,19 +294,7 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: if additional['tests'] > 0: md.append(f"- Test files: {additional['tests']}") md.append("") - - # Pending PRs - if ws['has_pending_prs']: - pr_links = [] - for pr_num in ws['pr_numbers']: - pr_url = f"https://github.com/{os.getenv('REPO_NAME')}/pull/{pr_num}" - pr_links.append(f"[#{pr_num}]({pr_url})") - - md.append(f"**Pending Updates:** ⚠️ Yes - {', '.join(pr_links)}") - else: - md.append("**Pending Updates:** ✅ No") - - md.append("") + md.append("---") md.append("") From fdba636b39648709d3e62d29a7e1296185fc1aa6 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 09:23:18 +0100 Subject: [PATCH 09/33] fix: Remove test branch trigger from workflow Remove the temporary feature/RHIDP-9450 branch trigger that was added for testing --- .github/workflows/update-wiki.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index ee3a9976..5ef0d13d 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -5,7 +5,8 @@ on: branches: - main - 'release-*' - + - 'feature/RHIDP-9450' + permissions: contents: write pull-requests: read From 35bbec0565da5b8da2d538fe9b8dae8bc4c0ac12 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 09:37:44 +0100 Subject: [PATCH 10/33] feat: Update table format with workspace and source links - Workspace column: links to overlay repo workspaces - Source column: repo@commit format linking to source workspace - Handle flat repos (link to root) vs workspace repos (link to workspace folder) - Maintain compact table format with key information --- .github/scripts/generate-wiki-page.py | 34 ++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 2d6c5431..ae888a17 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -209,26 +209,28 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Workspace Table md.append("## Workspace Overview") md.append("") - md.append("| Workspace | Source Repository | Pinned Commit | Backstage Version | Plugins | Pending Updates |") - md.append("|-----------|------------------|---------------|------------------|---------|----------------|") + md.append("| Workspace | Source | Backstage Version | Plugins | Pending Updates |") + md.append("|-----------|--------|------------------|---------|----------------|") for ws in workspaces_data: - # Workspace name - workspace_name = ws['name'] + # Workspace name - link to workspace in overlay repo + overlay_repo_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}" + workspace_name = f"[{ws['name']}]({overlay_repo_url})" - # Source Repository - if ws['repo_url']: + # Source - repo@commit linking to source workspace or repo root for flat repos + if ws['repo_url'] and ws['commit_sha']: repo_name = ws['repo_url'].replace('https://github.com/', '') - source_repo = f"[{repo_name}]({ws['repo_url']})" - else: - source_repo = "N/A" - - # Pinned Commit - if ws['commit_sha']: - commit_link = f"{ws['repo_url']}/commit/{ws['commit_sha']}" - pinned_commit = f"[{ws['commit_short']}]({commit_link})" + if ws['repo_flat']: + # Flat repos - link to repo root at commit + source_url = f"{ws['repo_url']}/tree/{ws['commit_sha']}" + source = f"[{repo_name}@{ws['commit_short']}]({source_url})" + else: + # Workspace-based repos - link to specific workspace folder + workspace_path = f"workspaces/{ws['name']}" + source_url = f"{ws['repo_url']}/tree/{ws['commit_sha']}/{workspace_path}" + source = f"[{repo_name}@{ws['commit_short']}]({source_url})" else: - pinned_commit = "N/A" + source = "N/A" # Backstage Version backstage_version = f"`{ws['backstage_version']}`" if ws['backstage_version'] else "N/A" @@ -247,7 +249,7 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: pending_updates = "✅ No" # Add table row - md.append(f"| {workspace_name} | {source_repo} | {pinned_commit} | {backstage_version} | {plugins_count} | {pending_updates} |") + md.append(f"| {workspace_name} | {source} | {backstage_version} | {plugins_count} | {pending_updates} |") md.append("") md.append("---") From 5b26e2da98f5a575f8a4e854e1115683f85cb986 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 09:51:14 +0100 Subject: [PATCH 11/33] feat: Refine wiki table content - Remove 'Pinned Commit' column (info now in Source column) - Add 'Commit Date' column - Update 'Source' column format: owner/repo@commit - Update 'Plugins' column: show list of plugins instead of count - Fetch package.json from source to get plugin name and version if possible --- .github/scripts/generate-wiki-page.py | 64 ++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index ae888a17..0f9e1c22 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -14,6 +14,7 @@ import json import yaml import subprocess +import requests from pathlib import Path from typing import Dict, List, Optional, Tuple from datetime import datetime, timezone @@ -88,6 +89,36 @@ def parse_plugins_list(workspace_path: Path) -> List[str]: return [] +def get_plugin_details(repo_url: str, commit_sha: str, plugin_path: str) -> str: + """ + Fetch package.json to get plugin name and version. + Returns 'name@version' or just the path if fetch fails. + """ + if not repo_url.startswith("https://github.com/"): + return plugin_path + + repo_name = repo_url.replace("https://github.com/", "") + + # Use GitHub API to get raw content + api_url = f"https://api.github.com/repos/{repo_name}/contents/{plugin_path}/package.json" + headers = { + "Accept": "application/vnd.github.v3.raw", + "Authorization": f"token {os.getenv('GH_TOKEN', '')}" + } + + try: + response = requests.get(api_url, headers=headers, params={"ref": commit_sha}, timeout=10) + if response.status_code == 200: + data = response.json() + name = data.get('name', 'unknown') + version = data.get('version', 'unknown') + return f"{name}@{version}" + except Exception as e: + print(f"Error fetching package.json for {plugin_path}: {e}", file=sys.stderr) + + return plugin_path + + def get_commit_details(repo_url: str, commit_sha: str) -> Tuple[str, str, str]: """ Get commit details including short SHA, commit message, and date. @@ -209,15 +240,15 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Workspace Table md.append("## Workspace Overview") md.append("") - md.append("| Workspace | Source | Backstage Version | Plugins | Pending Updates |") - md.append("|-----------|--------|------------------|---------|----------------|") + md.append("| Workspace | Source | Commit Date | Backstage Version | Plugins | Pending Updates |") + md.append("|-----------|--------|-------------|------------------|---------|----------------|") for ws in workspaces_data: # Workspace name - link to workspace in overlay repo overlay_repo_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}" workspace_name = f"[{ws['name']}]({overlay_repo_url})" - # Source - repo@commit linking to source workspace or repo root for flat repos + # Source - repo@commit linking to source workspace if ws['repo_url'] and ws['commit_sha']: repo_name = ws['repo_url'].replace('https://github.com/', '') if ws['repo_flat']: @@ -232,11 +263,17 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: else: source = "N/A" + # Commit Date + commit_date = ws['commit_date'] if ws['commit_date'] != "N/A" else "" + # Backstage Version backstage_version = f"`{ws['backstage_version']}`" if ws['backstage_version'] else "N/A" - # Plugins Count - plugins_count = f"{len(ws['plugins'])} plugins" + # Plugins List (formatted as list in cell) + if ws['plugins']: + plugins_list = "
".join([f"`{p}`" for p in ws['plugins']]) + else: + plugins_list = "No plugins" # Pending PRs if ws['has_pending_prs']: @@ -249,7 +286,7 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: pending_updates = "✅ No" # Add table row - md.append(f"| {workspace_name} | {source} | {backstage_version} | {plugins_count} | {pending_updates} |") + md.append(f"| {workspace_name} | {source} | {commit_date} | {backstage_version} | {plugins_list} | {pending_updates} |") md.append("") md.append("---") @@ -351,6 +388,19 @@ def main(): # Get Backstage version backstage_version = get_backstage_version(ws_path) + # Enhance plugin list with versions from package.json + enhanced_plugins = [] + if repo_url and commit_sha: + print(f" Fetching plugin details for {len(plugins)} plugins...") + for plugin_path in plugins: + # Fix path for flat repos if needed, but plugins-list usually has relative path from repo root + # However, in flat repos, the plugin path in plugins-list.yaml might be just "." or "plugins/x" + # We use it as is relative to repo root. + details = get_plugin_details(repo_url, commit_sha, plugin_path) + enhanced_plugins.append(details) + else: + enhanced_plugins = plugins + # Count additional files additional_files = count_additional_files(ws_path) @@ -366,7 +416,7 @@ def main(): 'commit_date': commit_date, 'repo_flat': repo_flat, 'backstage_version': backstage_version, - 'plugins': plugins, + 'plugins': enhanced_plugins, 'additional_files': additional_files, 'has_pending_prs': has_pending_prs, 'pr_numbers': pr_numbers From c46ad99bce6e43e02107c66f1987480e2d0172bd Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 10:08:57 +0100 Subject: [PATCH 12/33] feat: Finalize wiki table format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add repo structure icon (🌳/📄) with link to source.json and tooltip - Format date to show only YYYY-MM-DD - List plugins with package name and version - Keep compact table layout - Ensure links work correctly for all repo types --- .github/scripts/generate-wiki-page.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 0f9e1c22..de1bf0b3 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -240,10 +240,23 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Workspace Table md.append("## Workspace Overview") md.append("") - md.append("| Workspace | Source | Commit Date | Backstage Version | Plugins | Pending Updates |") - md.append("|-----------|--------|-------------|------------------|---------|----------------|") + md.append("| Type | Workspace | Source | Commit Date | Backstage Version | Plugins | Pending Updates |") + md.append("|:----:|-----------|--------|-------------|------------------|---------|----------------|") for ws in workspaces_data: + # Repo Structure Icon & Link + # 🌳 for Monorepo (workspace-based), 📄 for Flat (root-level plugins) + source_json_url = f"https://github.com/{os.getenv('REPO_NAME')}/blob/{branch_name}/workspaces/{ws['name']}/source.json" + if ws['repo_flat']: + struct_icon = "📄" + struct_tooltip = "Flat (root-level plugins)" + else: + struct_icon = "🌳" + struct_tooltip = "Monorepo (workspace-based)" + + # Markdown link with tooltip: [icon](url "tooltip") + structure = f"[{struct_icon}]({source_json_url} \"{struct_tooltip}\")" + # Workspace name - link to workspace in overlay repo overlay_repo_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}" workspace_name = f"[{ws['name']}]({overlay_repo_url})" @@ -264,13 +277,15 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: source = "N/A" # Commit Date - commit_date = ws['commit_date'] if ws['commit_date'] != "N/A" else "" + commit_date = ws['commit_date'].split(' ')[0] if ws['commit_date'] != "N/A" else "" # Backstage Version backstage_version = f"`{ws['backstage_version']}`" if ws['backstage_version'] else "N/A" - # Plugins List (formatted as list in cell) + # Plugins List + # Format: @ if ws['plugins']: + # Use
for line breaks within a table cell plugins_list = "
".join([f"`{p}`" for p in ws['plugins']]) else: plugins_list = "No plugins" @@ -286,7 +301,7 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: pending_updates = "✅ No" # Add table row - md.append(f"| {workspace_name} | {source} | {commit_date} | {backstage_version} | {plugins_list} | {pending_updates} |") + md.append(f"| {structure} | {workspace_name} | {source} | {commit_date} | {backstage_version} | {plugins_list} | {pending_updates} |") md.append("") md.append("---") From 28f0fde440f734127c4f77fb0cf95afc3f69ba1d Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 10:26:46 +0100 Subject: [PATCH 13/33] feat: Add text labels to icons in wiki table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Patches: '🩹 Has patches' instead of just icon - Overlays: '🔄 Has overlays' instead of just icon - Support: '✅ Supported' / '🤝 Community' instead of just icons - Use
to stack badges in Type column --- .github/scripts/generate-wiki-page.py | 112 ++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 7 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index de1bf0b3..7b60b727 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -205,6 +205,54 @@ def get_backstage_version(workspace_path: Path) -> Optional[str]: return None +def load_plugin_lists() -> Tuple[List[str], List[str]]: + """Load the list of supported and community plugins.""" + supported_plugins = [] + community_plugins = [] + + try: + if Path("rhdh-supported-plugins.txt").exists(): + with open("rhdh-supported-plugins.txt", 'r') as f: + supported_plugins = [line.strip() for line in f if line.strip() and not line.startswith('#')] + + if Path("rhdh-community-plugins.txt").exists(): + with open("rhdh-community-plugins.txt", 'r') as f: + community_plugins = [line.strip() for line in f if line.strip() and not line.startswith('#')] + except Exception as e: + print(f"Error loading plugin lists: {e}", file=sys.stderr) + + return supported_plugins, community_plugins + + +def check_support_status(plugin_path: str, workspace_name: str, supported_list: List[str], community_list: List[str]) -> str: + """ + Check support status for a plugin. + Returns 'Supported', 'Community', or 'Unknown' + The path in the text files is typically / + """ + # Construct the full path as expected in the text files + # workspace_name is like "acr" + # plugin_path is like "plugins/acr" (relative to workspace root) + # Full path in text file: "acr/plugins/acr" + + # Handle potential leading slash or ./ in plugin_path + clean_plugin_path = plugin_path.lstrip('./').lstrip('/') + full_path = f"{workspace_name}/{clean_plugin_path}" + + if full_path in supported_list: + return "Supported" + if full_path in community_list: + return "Community" + + # Try without workspace prefix just in case + if clean_plugin_path in supported_list: + return "Supported" + if clean_plugin_path in community_list: + return "Community" + + return "Unknown" + + def count_additional_files(workspace_path: Path) -> Dict[str, int]: """Count additional configuration files in the workspace.""" counts = { @@ -254,13 +302,31 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: struct_icon = "🌳" struct_tooltip = "Monorepo (workspace-based)" - # Markdown link with tooltip: [icon](url "tooltip") - structure = f"[{struct_icon}]({source_json_url} \"{struct_tooltip}\")" + # Additional checks for icon badges + # Check for patches + has_patches = ws['additional_files']['patches'] > 0 + # Check for overlays (plugin overlays) + has_overlays = ws['additional_files']['plugins'] > 0 + + structure_badges = [] + structure_badges.append(f"[{struct_icon}]({source_json_url} \"{struct_tooltip}\")") + + if has_patches: + structure_badges.append("🩹 Has patches") + if has_overlays: + structure_badges.append("🔄 Has overlays") + + structure = "
".join(structure_badges) # Workspace name - link to workspace in overlay repo overlay_repo_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}" workspace_name = f"[{ws['name']}]({overlay_repo_url})" + # Check for missing metadata + has_metadata = ws['additional_files']['metadata'] > 0 + if not has_metadata: + workspace_name = f"🔴 {workspace_name}" + # Source - repo@commit linking to source workspace if ws['repo_url'] and ws['commit_sha']: repo_name = ws['repo_url'].replace('https://github.com/', '') @@ -283,10 +349,23 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: backstage_version = f"`{ws['backstage_version']}`" if ws['backstage_version'] else "N/A" # Plugins List - # Format: @ + # Format: @ [Support Status] if ws['plugins']: - # Use
for line breaks within a table cell - plugins_list = "
".join([f"`{p}`" for p in ws['plugins']]) + plugins_list_items = [] + for p in ws['plugins']: + # p is now a dict with details, path, status + name_ver = p['details'] + status = p['status'] + + status_icon = "" + if status == "Supported": + status_icon = "✅ Supported" + elif status == "Community": + status_icon = "🤝 Community" + + plugins_list_items.append(f"`{name_ver}` {status_icon}") + + plugins_list = "
".join(plugins_list_items) else: plugins_list = "No plugins" @@ -371,6 +450,10 @@ def main(): workspace_names = get_workspace_list(workspaces_dir) print(f"Found {len(workspace_names)} workspaces") + # Load support lists + supported_plugins, community_plugins = load_plugin_lists() + print(f"Loaded {len(supported_plugins)} supported and {len(community_plugins)} community plugins") + # Collect data for each workspace workspaces_data = [] @@ -412,9 +495,24 @@ def main(): # However, in flat repos, the plugin path in plugins-list.yaml might be just "." or "plugins/x" # We use it as is relative to repo root. details = get_plugin_details(repo_url, commit_sha, plugin_path) - enhanced_plugins.append(details) + + # Check support status + support_status = check_support_status(plugin_path, ws_name, supported_plugins, community_plugins) + + enhanced_plugins.append({ + 'details': details, + 'path': plugin_path, + 'status': support_status + }) else: - enhanced_plugins = plugins + # If fetch fails or no repo info, create basic objects + for plugin_path in plugins: + support_status = check_support_status(plugin_path, ws_name, supported_plugins, community_plugins) + enhanced_plugins.append({ + 'details': plugin_path, + 'path': plugin_path, + 'status': support_status + }) # Count additional files additional_files = count_additional_files(ws_path) From 1cafcf22691c44c2ad7a0edd501248465b50d33d Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 10:33:16 +0100 Subject: [PATCH 14/33] feat: Add text label to missing metadata indicator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show '🔴 Missing metadata' instead of just red circle - Stack label above workspace name using
for clarity --- .github/scripts/generate-wiki-page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 7b60b727..893415e7 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -325,7 +325,7 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Check for missing metadata has_metadata = ws['additional_files']['metadata'] > 0 if not has_metadata: - workspace_name = f"🔴 {workspace_name}" + workspace_name = f"🔴 Missing metadata
{workspace_name}" # Source - repo@commit linking to source workspace if ws['repo_url'] and ws['commit_sha']: From 6b60ce0928dd5c831a548f4c6ae09708ed8c85c1 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 10:46:00 +0100 Subject: [PATCH 15/33] feat: Add separate Metadata column instead of inline indicator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove metadata indicator from workspace name - Add dedicated 'Metadata' column showing '✅ Available' or '🔴 Missing' - Keep workspace names clean while clearly indicating metadata status --- .github/scripts/generate-wiki-page.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 893415e7..bb91cbd6 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -288,8 +288,8 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Workspace Table md.append("## Workspace Overview") md.append("") - md.append("| Type | Workspace | Source | Commit Date | Backstage Version | Plugins | Pending Updates |") - md.append("|:----:|-----------|--------|-------------|------------------|---------|----------------|") + md.append("| Type | Workspace | Metadata | Source | Commit Date | Backstage Version | Plugins | Pending Updates |") + md.append("|:----:|-----------|:--------:|--------|-------------|------------------|---------|----------------|") for ws in workspaces_data: # Repo Structure Icon & Link @@ -322,10 +322,12 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: overlay_repo_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}" workspace_name = f"[{ws['name']}]({overlay_repo_url})" - # Check for missing metadata + # Metadata status has_metadata = ws['additional_files']['metadata'] > 0 - if not has_metadata: - workspace_name = f"🔴 Missing metadata
{workspace_name}" + if has_metadata: + metadata_status = "✅ Available" + else: + metadata_status = "🔴 Missing" # Source - repo@commit linking to source workspace if ws['repo_url'] and ws['commit_sha']: @@ -380,7 +382,7 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: pending_updates = "✅ No" # Add table row - md.append(f"| {structure} | {workspace_name} | {source} | {commit_date} | {backstage_version} | {plugins_list} | {pending_updates} |") + md.append(f"| {structure} | {workspace_name} | {metadata_status} | {source} | {commit_date} | {backstage_version} | {plugins_list} | {pending_updates} |") md.append("") md.append("---") From 2e06336210ddd9e15367f83ebce2c1a97f9870e3 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 10:57:49 +0100 Subject: [PATCH 16/33] feat: Add 'Unknown' status for plugins not in support lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show '❓ Unknown' for plugins not listed in rhdh-supported-plugins.txt or rhdh-community-plugins.txt - Makes it clear when plugins have undefined support status - Improves table clarity by explicitly labeling all plugins --- .github/scripts/generate-wiki-page.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index bb91cbd6..bcd215f5 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -364,6 +364,8 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: status_icon = "✅ Supported" elif status == "Community": status_icon = "🤝 Community" + else: + status_icon = "❓ Unknown" plugins_list_items.append(f"`{name_ver}` {status_icon}") From 65d643b8b619f34271690e32dbcf0a0cc5f8b713 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 13:40:35 +0100 Subject: [PATCH 17/33] feat: Improve plugin list visibility with bullet points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace
line breaks with • bullet points for better readability - Keep full text labels (✅ Supported, 🤝 Community) - Make plugin lists more compact and scannable in table cells --- .github/scripts/generate-wiki-page.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index bcd215f5..7e78430b 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -358,18 +358,17 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # p is now a dict with details, path, status name_ver = p['details'] status = p['status'] - + status_icon = "" if status == "Supported": status_icon = "✅ Supported" elif status == "Community": status_icon = "🤝 Community" - else: - status_icon = "❓ Unknown" - - plugins_list_items.append(f"`{name_ver}` {status_icon}") - - plugins_list = "
".join(plugins_list_items) + + plugins_list_items.append(f"`{name_ver}`{status_icon}") + + # Use bullet points (•) for better visibility instead of
+ plugins_list = " • ".join(plugins_list_items) else: plugins_list = "No plugins" @@ -495,9 +494,6 @@ def main(): if repo_url and commit_sha: print(f" Fetching plugin details for {len(plugins)} plugins...") for plugin_path in plugins: - # Fix path for flat repos if needed, but plugins-list usually has relative path from repo root - # However, in flat repos, the plugin path in plugins-list.yaml might be just "." or "plugins/x" - # We use it as is relative to repo root. details = get_plugin_details(repo_url, commit_sha, plugin_path) # Check support status From 46cf1dd76e43c68549c0fe6c3e57f29256003073 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 13:47:14 +0100 Subject: [PATCH 18/33] feat: Enhance plugin list visibility with line breaks and icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use
line breaks with 🔸 bullet points for better readability - Keep vertical layout but make it more prominent - Each plugin on its own line with clear visual separation --- .github/scripts/generate-wiki-page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 7e78430b..0eb78d87 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -367,8 +367,8 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: plugins_list_items.append(f"`{name_ver}`{status_icon}") - # Use bullet points (•) for better visibility instead of
- plugins_list = " • ".join(plugins_list_items) + # Use line breaks with bullet points for better visibility + plugins_list = "
🔸 ".join([""] + plugins_list_items) else: plugins_list = "No plugins" From e2d945404f7b7af4c03da4774673a4f070b1e434 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 14:44:16 +0100 Subject: [PATCH 19/33] fix: Filter pending PRs by target branch and improve path check - Add base branch filtering to gh pr list to only show PRs targeting the current branch - Update jq query to correctly check if any file in the PR touches the workspace path - Pass branch_name to check_pending_prs function --- .github/scripts/generate-wiki-page.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 0eb78d87..3a1d1009 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -159,7 +159,7 @@ def get_commit_details(repo_url: str, commit_sha: str) -> Tuple[str, str, str]: return commit_sha[:7], "N/A", "N/A" -def check_pending_prs(workspace_name: str, repo_name: str) -> Tuple[bool, List[str]]: +def check_pending_prs(workspace_name: str, repo_name: str, target_branch: str) -> Tuple[bool, List[str]]: """ Check if there are any open PRs that modify this workspace. Returns (has_pending, [pr_numbers]) @@ -167,12 +167,15 @@ def check_pending_prs(workspace_name: str, repo_name: str) -> Tuple[bool, List[s workspace_path = f"workspaces/{workspace_name}" # Use gh CLI to search for open PRs + # Filter by base branch and check if files touch the workspace cmd = [ "gh", "pr", "list", "--repo", repo_name, + "--base", target_branch, "--state", "open", + "--limit", "100", "--json", "number,files", - "--jq", f'.[] | select(.files[].path | startswith("{workspace_path}")) | .number' + "--jq", f'.[] | select(any(.files[]; .path | startswith("{workspace_path}"))) | .number' ] exit_code, stdout, stderr = run_command(cmd, check=False) @@ -518,7 +521,7 @@ def main(): additional_files = count_additional_files(ws_path) # Check for pending PRs - has_pending_prs, pr_numbers = check_pending_prs(ws_name, repo_name) + has_pending_prs, pr_numbers = check_pending_prs(ws_name, repo_name, branch_name) workspaces_data.append({ 'name': ws_name, From 9eef9d7147e07c9cc2ad68af5116e1eefe0b61fc Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 15:08:24 +0100 Subject: [PATCH 20/33] Update wiki page generator to show pending PRs with links - Remove 'Pending Updates' column from workspace table - Add pending PR detection and display with warning icon - Include clickable PR links in the workspace overview --- .github/scripts/generate-wiki-page.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 3a1d1009..24025a90 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -291,8 +291,8 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Workspace Table md.append("## Workspace Overview") md.append("") - md.append("| Type | Workspace | Metadata | Source | Commit Date | Backstage Version | Plugins | Pending Updates |") - md.append("|:----:|-----------|:--------:|--------|-------------|------------------|---------|----------------|") + md.append("| Type | Workspace | Metadata | Source | Commit Date | Backstage Version | Plugins |") + md.append("|:----:|-----------|:--------:|--------|-------------|------------------|---------|") for ws in workspaces_data: # Repo Structure Icon & Link @@ -311,6 +311,9 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Check for overlays (plugin overlays) has_overlays = ws['additional_files']['plugins'] > 0 + # Check for pending PRs + has_prs = ws['has_pending_prs'] + structure_badges = [] structure_badges.append(f"[{struct_icon}]({source_json_url} \"{struct_tooltip}\")") @@ -318,6 +321,15 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: structure_badges.append("🩹 Has patches") if has_overlays: structure_badges.append("🔄 Has overlays") + if has_prs: + # Add red warning icon with links to PRs + pr_links = [] + for pr_num in ws['pr_numbers']: + pr_url = f"https://github.com/{os.getenv('REPO_NAME')}/pull/{pr_num}" + pr_links.append(f"[#{pr_num}]({pr_url})") + + pr_text = ", ".join(pr_links) + structure_badges.append(f"🚨 Pending PRs: {pr_text}") structure = "
".join(structure_badges) From 2743ce15406164609e504f98f3c9f319a1ff9ddb Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 15:25:06 +0100 Subject: [PATCH 21/33] feat: consolidate table icons and highlight overrides - Combine structure, overlays, patches, metadata, and pending PRs into the Type column with tooltips and links - Remove redundant metadata column and detailed section to keep table compact - Fetch upstream backstage version and highlight when overlay overrides it - Improve plugin list readability with bullet-styled line breaks - Filter pending PRs by base branch --- .github/scripts/generate-wiki-page.py | 108 +++++++++++--------------- 1 file changed, 44 insertions(+), 64 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 24025a90..5078f286 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -208,6 +208,29 @@ def get_backstage_version(workspace_path: Path) -> Optional[str]: return None +def get_source_backstage_version(repo_url: str, commit_sha: str) -> Optional[str]: + """Fetch backstage.json from the source repo to determine its version.""" + if not repo_url or not commit_sha or not repo_url.startswith("https://github.com/"): + return None + + repo_name = repo_url.replace("https://github.com/", "").rstrip('/') + api_url = f"https://api.github.com/repos/{repo_name}/contents/backstage.json" + headers = { + "Accept": "application/vnd.github.v3.raw", + "Authorization": f"token {os.getenv('GH_TOKEN', '')}" + } + + try: + response = requests.get(api_url, headers=headers, params={"ref": commit_sha}, timeout=10) + if response.status_code == 200: + data = response.json() + return data.get("version") + except Exception as e: + print(f"Error fetching upstream backstage.json for {repo_url}: {e}", file=sys.stderr) + + return None + + def load_plugin_lists() -> Tuple[List[str], List[str]]: """Load the list of supported and community plugins.""" supported_plugins = [] @@ -291,8 +314,8 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Workspace Table md.append("## Workspace Overview") md.append("") - md.append("| Type | Workspace | Metadata | Source | Commit Date | Backstage Version | Plugins |") - md.append("|:----:|-----------|:--------:|--------|-------------|------------------|---------|") + md.append("| Type | Workspace | Source | Commit Date | Backstage Version | Plugins |") + md.append("|:----:|-----------|--------|-------------|------------------|---------|") for ws in workspaces_data: # Repo Structure Icon & Link @@ -337,12 +360,12 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: overlay_repo_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}" workspace_name = f"[{ws['name']}]({overlay_repo_url})" - # Metadata status + # Metadata status icon has_metadata = ws['additional_files']['metadata'] > 0 if has_metadata: - metadata_status = "✅ Available" + structure_badges.append('🟢') else: - metadata_status = "🔴 Missing" + structure_badges.append('🔴') # Source - repo@commit linking to source workspace if ws['repo_url'] and ws['commit_sha']: @@ -362,8 +385,18 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Commit Date commit_date = ws['commit_date'].split(' ')[0] if ws['commit_date'] != "N/A" else "" - # Backstage Version - backstage_version = f"`{ws['backstage_version']}`" if ws['backstage_version'] else "N/A" + # Backstage Version (prefer source repo version, highlight overrides) + overlay_version = ws.get('overlay_backstage_version') + source_version = ws.get('source_backstage_version') + display_version = source_version or overlay_version + if display_version: + backstage_version = f"`{display_version}`" + else: + backstage_version = "N/A" + + if overlay_version and source_version and overlay_version != source_version: + tooltip = f"Overlay overrides upstream version to {overlay_version}".replace('"', '"') + backstage_version = f'{backstage_version} 🔧' # Plugins List # Format: @ [Support Status] @@ -387,68 +420,13 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: else: plugins_list = "No plugins" - # Pending PRs - if ws['has_pending_prs']: - pr_links = [] - for pr_num in ws['pr_numbers']: - pr_url = f"https://github.com/{os.getenv('REPO_NAME')}/pull/{pr_num}" - pr_links.append(f"[#{pr_num}]({pr_url})") - pending_updates = f"⚠️ {', '.join(pr_links)}" - else: - pending_updates = "✅ No" - # Add table row - md.append(f"| {structure} | {workspace_name} | {metadata_status} | {source} | {commit_date} | {backstage_version} | {plugins_list} | {pending_updates} |") + md.append(f"| {structure} | {workspace_name} | {source} | {commit_date} | {backstage_version} | {plugins_list} |") md.append("") md.append("---") md.append("") - # Detailed Workspace Information - md.append("## Detailed Workspace Information") - md.append("") - for ws in workspaces_data: - md.append(f"### {ws['name']}") - md.append("") - - # Commit Details (if available) - if ws['commit_sha'] and ws['commit_message'] != "N/A": - md.append(f"**Latest Commit:** {ws['commit_message']}") - if ws['commit_date'] != "N/A": - md.append(f"**Commit Date:** {ws['commit_date']}") - md.append("") - - # Repository Structure - workspace_type = "Monorepo (workspace-based)" if not ws['repo_flat'] else "Flat (root-level plugins)" - md.append(f"**Repository Structure:** {workspace_type}") - md.append("") - - # Plugin List - if ws['plugins']: - md.append("**Plugins:**") - md.append("") - for plugin in ws['plugins']: - md.append(f"- `{plugin}`") - md.append("") - - # Additional Files - additional = ws['additional_files'] - if any(additional.values()): - md.append("**Additional Configuration:**") - md.append("") - if additional['metadata'] > 0: - md.append(f"- Metadata files: {additional['metadata']}") - if additional['plugins'] > 0: - md.append(f"- Plugin overlays: {additional['plugins']}") - if additional['patches'] > 0: - md.append(f"- Patches: {additional['patches']}") - if additional['tests'] > 0: - md.append(f"- Test files: {additional['tests']}") - md.append("") - - md.append("---") - md.append("") - return '\n'.join(md) @@ -503,6 +481,7 @@ def main(): # Get Backstage version backstage_version = get_backstage_version(ws_path) + source_backstage_version = get_source_backstage_version(repo_url, commit_sha) # Enhance plugin list with versions from package.json enhanced_plugins = [] @@ -543,7 +522,8 @@ def main(): 'commit_message': commit_message, 'commit_date': commit_date, 'repo_flat': repo_flat, - 'backstage_version': backstage_version, + 'overlay_backstage_version': backstage_version, + 'source_backstage_version': source_backstage_version, 'plugins': enhanced_plugins, 'additional_files': additional_files, 'has_pending_prs': has_pending_prs, From 9eae1488fde2030c008d72973ad3f5639e267fe3 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 15:52:54 +0100 Subject: [PATCH 22/33] feat: add label filtering to PR detection - Filter pending PRs by workspace_addition or workspace_update labels - Ensures only relevant workspace PRs are shown in the wiki table - Already filtering by target branch and workspace path --- .github/scripts/generate-wiki-page.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 5078f286..12f62982 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -162,20 +162,24 @@ def get_commit_details(repo_url: str, commit_sha: str) -> Tuple[str, str, str]: def check_pending_prs(workspace_name: str, repo_name: str, target_branch: str) -> Tuple[bool, List[str]]: """ Check if there are any open PRs that modify this workspace. + Filters by: + - Base branch (target_branch) + - Files touching the workspace path + - Labels: workspace_addition or workspace_update Returns (has_pending, [pr_numbers]) """ workspace_path = f"workspaces/{workspace_name}" # Use gh CLI to search for open PRs - # Filter by base branch and check if files touch the workspace + # Filter by base branch, workspace path, and labels cmd = [ "gh", "pr", "list", "--repo", repo_name, "--base", target_branch, "--state", "open", "--limit", "100", - "--json", "number,files", - "--jq", f'.[] | select(any(.files[]; .path | startswith("{workspace_path}"))) | .number' + "--json", "number,files,labels", + "--jq", f'.[] | select(any(.files[]; .path | startswith("{workspace_path}")) and (any(.labels[]; .name == "workspace_addition" or .name == "workspace_update"))) | .number' ] exit_code, stdout, stderr = run_command(cmd, check=False) From ecd3cf4b90dad1a4b4c1b11fb00f215bcd36be04 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 16:05:47 +0100 Subject: [PATCH 23/33] refactor: consolidate PR indicator into Type column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove separate Pending Updates column to save space - Add red circle icon (🔴) in Type column when PR exists - Icon links directly to the PR and shows tooltip with PR number - Cleaner icon-based indicators with tooltips for all status items - Table now has 6 columns instead of 7 --- .github/scripts/generate-wiki-page.py | 51 +++++++++++++-------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 12f62982..b8b791f0 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -332,31 +332,37 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: struct_icon = "🌳" struct_tooltip = "Monorepo (workspace-based)" - # Additional checks for icon badges - # Check for patches - has_patches = ws['additional_files']['patches'] > 0 - # Check for overlays (plugin overlays) - has_overlays = ws['additional_files']['plugins'] > 0 - - # Check for pending PRs - has_prs = ws['has_pending_prs'] - + # Build Type column with all status icons structure_badges = [] + + # 1. Repository structure icon (linked to source.json) structure_badges.append(f"[{struct_icon}]({source_json_url} \"{struct_tooltip}\")") + # 2. Patches indicator + has_patches = ws['additional_files']['patches'] > 0 if has_patches: - structure_badges.append("🩹 Has patches") + structure_badges.append('🩹') + + # 3. Overlays indicator + has_overlays = ws['additional_files']['plugins'] > 0 if has_overlays: - structure_badges.append("🔄 Has overlays") + structure_badges.append('🔄') + + # 4. Metadata status + has_metadata = ws['additional_files']['metadata'] > 0 + if has_metadata: + structure_badges.append('🟢') + else: + structure_badges.append('🔴') + + # 5. Pending PR indicator (red icon linking to PR) + has_prs = ws['has_pending_prs'] if has_prs: - # Add red warning icon with links to PRs - pr_links = [] - for pr_num in ws['pr_numbers']: - pr_url = f"https://github.com/{os.getenv('REPO_NAME')}/pull/{pr_num}" - pr_links.append(f"[#{pr_num}]({pr_url})") - - pr_text = ", ".join(pr_links) - structure_badges.append(f"🚨 Pending PRs: {pr_text}") + # Link the red icon to the first PR (usually only 1 expected) + pr_num = ws['pr_numbers'][0] + pr_url = f"https://github.com/{os.getenv('REPO_NAME')}/pull/{pr_num}" + pr_tooltip = f"Pending update PR #{pr_num}" + structure_badges.append(f'[🔴]({pr_url})') structure = "
".join(structure_badges) @@ -364,13 +370,6 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: overlay_repo_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}" workspace_name = f"[{ws['name']}]({overlay_repo_url})" - # Metadata status icon - has_metadata = ws['additional_files']['metadata'] > 0 - if has_metadata: - structure_badges.append('🟢') - else: - structure_badges.append('🔴') - # Source - repo@commit linking to source workspace if ws['repo_url'] and ws['commit_sha']: repo_name = ws['repo_url'].replace('https://github.com/', '') From f202226d0245de1044a09be2ec01d549faae88fd Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 16:20:13 +0100 Subject: [PATCH 24/33] feat: highlight Backstage version overrides with color - Always display source repo version from backstage.json at pinned commit - Show version in orange (#ff6b35) when overlay overrides it - Add tooltip explaining the override (upstream version -> overlay version) - Normal black text when no override exists --- .github/scripts/generate-wiki-page.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index b8b791f0..fb5f0598 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -388,19 +388,23 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Commit Date commit_date = ws['commit_date'].split(' ')[0] if ws['commit_date'] != "N/A" else "" - # Backstage Version (prefer source repo version, highlight overrides) + # Backstage Version (show source repo version, highlight overrides with color) overlay_version = ws.get('overlay_backstage_version') source_version = ws.get('source_backstage_version') display_version = source_version or overlay_version + if display_version: - backstage_version = f"`{display_version}`" + # Check if there's an override + if overlay_version and source_version and overlay_version != source_version: + # Override detected - show in orange/red with tooltip + tooltip = f"Overlay overrides upstream version {source_version} to {overlay_version}".replace('"', '"') + backstage_version = f'`{display_version}`' + else: + # No override - normal display + backstage_version = f"`{display_version}`" else: backstage_version = "N/A" - if overlay_version and source_version and overlay_version != source_version: - tooltip = f"Overlay overrides upstream version to {overlay_version}".replace('"', '"') - backstage_version = f'{backstage_version} 🔧' - # Plugins List # Format: @ [Support Status] if ws['plugins']: From 7bd0c3a304d11a3f0f6ad11c56465ab70ba5ac20 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 16:26:45 +0100 Subject: [PATCH 25/33] refactor: make table more compact to fit screen width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Shorten column headers: 'Type' -> '🔍', 'Commit Date' -> 'Date', 'Backstage Version' -> 'Backstage' - Use tags for commit dates to reduce text size - Remove detailed workspace information section (already done) - Table now fits better on standard screen widths --- .github/scripts/generate-wiki-page.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index fb5f0598..a3e12a9e 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -318,8 +318,9 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Workspace Table md.append("## Workspace Overview") md.append("") - md.append("| Type | Workspace | Source | Commit Date | Backstage Version | Plugins |") - md.append("|:----:|-----------|--------|-------------|------------------|---------|") + # Use shorter column headers and sub tags for compact display + md.append("| 🔍 | Workspace | Source | Date | Backstage | Plugins |") + md.append("|:----:|-----------|--------|------|-----------|---------|") for ws in workspaces_data: # Repo Structure Icon & Link @@ -385,10 +386,13 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: else: source = "N/A" - # Commit Date - commit_date = ws['commit_date'].split(' ')[0] if ws['commit_date'] != "N/A" else "" + # Commit Date (use smaller text for compactness) + if ws['commit_date'] != "N/A": + commit_date = f"{ws['commit_date'].split(' ')[0]}" + else: + commit_date = "" - # Backstage Version (show source repo version, highlight overrides with color) + # Backstage Version (show source repo version, highlight overrides with warning icon) overlay_version = ws.get('overlay_backstage_version') source_version = ws.get('source_backstage_version') display_version = source_version or overlay_version @@ -396,9 +400,9 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: if display_version: # Check if there's an override if overlay_version and source_version and overlay_version != source_version: - # Override detected - show in orange/red with tooltip + # Override detected - show with warning icon and tooltip tooltip = f"Overlay overrides upstream version {source_version} to {overlay_version}".replace('"', '"') - backstage_version = f'`{display_version}`' + backstage_version = f'`{display_version}` ⚠️' else: # No override - normal display backstage_version = f"`{display_version}`" From a8b7cc94b8b02a104253d01e448061d103fab5c4 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 16:35:44 +0100 Subject: [PATCH 26/33] refactor: remove Commit Date column from table - Commit date is visible when clicking through to the source link - Reduces table width for better screen fit - Table now has 5 columns: Type, Workspace, Source, Backstage Version, Plugins --- .github/scripts/generate-wiki-page.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index a3e12a9e..737084c0 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -318,9 +318,8 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Workspace Table md.append("## Workspace Overview") md.append("") - # Use shorter column headers and sub tags for compact display - md.append("| 🔍 | Workspace | Source | Date | Backstage | Plugins |") - md.append("|:----:|-----------|--------|------|-----------|---------|") + md.append("| Type | Workspace | Source | Backstage Version | Plugins |") + md.append("|:----:|-----------|--------|------------------|---------|") for ws in workspaces_data: # Repo Structure Icon & Link @@ -386,12 +385,6 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: else: source = "N/A" - # Commit Date (use smaller text for compactness) - if ws['commit_date'] != "N/A": - commit_date = f"{ws['commit_date'].split(' ')[0]}" - else: - commit_date = "" - # Backstage Version (show source repo version, highlight overrides with warning icon) overlay_version = ws.get('overlay_backstage_version') source_version = ws.get('source_backstage_version') @@ -432,7 +425,7 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: plugins_list = "No plugins" # Add table row - md.append(f"| {structure} | {workspace_name} | {source} | {commit_date} | {backstage_version} | {plugins_list} |") + md.append(f"| {structure} | {workspace_name} | {source} | {backstage_version} | {plugins_list} |") md.append("") md.append("---") From 941ebeabde5a1a330bdb514924cf9c7444459907 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 16:41:23 +0100 Subject: [PATCH 27/33] feat: improve plugin list formatting with distinct status icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use 🔴 for Red Hat Supported (with tooltip) - Use 🟡 for Community/DevPreview (with tooltip) - Use ▪️ for Pure Community/Unknown (with tooltip) - Remove text labels to save space - Format: --- .github/scripts/generate-wiki-page.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 737084c0..5fc32dca 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -411,16 +411,26 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: name_ver = p['details'] status = p['status'] - status_icon = "" + # Define icon and tooltip based on status if status == "Supported": - status_icon = "✅ Supported" + # Red Hat Supported + icon = "🔴" + tooltip = "Red Hat Supported" elif status == "Community": - status_icon = "🤝 Community" - - plugins_list_items.append(f"`{name_ver}`{status_icon}") - - # Use line breaks with bullet points for better visibility - plugins_list = "
🔸 ".join([""] + plugins_list_items) + # Community / Dev Preview + icon = "🟡" + tooltip = "Community / Dev Preview" + else: + # Pure Community / Unknown + icon = "▪️" + tooltip = "Community / Unknown" + + # Format: + plugin_entry = f'{icon} `{name_ver}`' + plugins_list_items.append(plugin_entry) + + # Join with line breaks + plugins_list = "
".join(plugins_list_items) else: plugins_list = "No plugins" From d83fa6fe51435a7922eccafb7abbc8e6c7acd5c2 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 16:44:01 +0100 Subject: [PATCH 28/33] fix: change supported plugin icon to green MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change Red Hat Supported icon from 🔴 to 🟢 to avoid confusion with error/warning states - 🟢 = Supported (Safe/Stable) - 🟡 = Community (Preview/Caution) - ▪️ = Unknown (Neutral) --- .github/scripts/generate-wiki-page.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 5fc32dca..96e5c805 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -413,15 +413,15 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # Define icon and tooltip based on status if status == "Supported": - # Red Hat Supported - icon = "🔴" + # Red Hat Supported - Use Green for positive status + icon = "🟢" tooltip = "Red Hat Supported" elif status == "Community": - # Community / Dev Preview + # Community / Dev Preview - Use Yellow for caution/preview icon = "🟡" tooltip = "Community / Dev Preview" else: - # Pure Community / Unknown + # Pure Community / Unknown - Neutral icon = "▪️" tooltip = "Community / Unknown" From 2606180e623742f4c1f2c678a9dce1156a19cf7a Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Wed, 19 Nov 2025 17:00:33 +0100 Subject: [PATCH 29/33] fix: plugin path for monorepos and reduce list font size - Fix incorrect package.json fetching for workspace-based repos by prepending workspaces/{name}/ - Reduce plugin list text size using tags for better table fit --- .github/scripts/generate-wiki-page.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 96e5c805..721d6be9 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -338,15 +338,17 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: # 1. Repository structure icon (linked to source.json) structure_badges.append(f"[{struct_icon}]({source_json_url} \"{struct_tooltip}\")") - # 2. Patches indicator + # 2. Patches indicator (link to patches directory) has_patches = ws['additional_files']['patches'] > 0 if has_patches: - structure_badges.append('🩹') + patches_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}/patches" + structure_badges.append(f'[🩹]({patches_url})') - # 3. Overlays indicator + # 3. Overlays indicator (link to plugins directory) has_overlays = ws['additional_files']['plugins'] > 0 if has_overlays: - structure_badges.append('🔄') + plugins_url = f"https://github.com/{os.getenv('REPO_NAME')}/tree/{branch_name}/workspaces/{ws['name']}/plugins" + structure_badges.append(f'[🔄]({plugins_url})') # 4. Metadata status has_metadata = ws['additional_files']['metadata'] > 0 @@ -426,7 +428,8 @@ def generate_markdown(branch_name: str, workspaces_data: List[Dict]) -> str: tooltip = "Community / Unknown" # Format: - plugin_entry = f'{icon} `{name_ver}`' + # Use sub tag for smaller text + plugin_entry = f'{icon} `{name_ver}`' plugins_list_items.append(plugin_entry) # Join with line breaks @@ -502,7 +505,16 @@ def main(): if repo_url and commit_sha: print(f" Fetching plugin details for {len(plugins)} plugins...") for plugin_path in plugins: - details = get_plugin_details(repo_url, commit_sha, plugin_path) + # Determine full path in source repo + # If it's a workspace-based monorepo (not flat), path is workspaces/{ws_name}/{plugin_path} + if not repo_flat: + # Remove any ./ prefix to be safe + clean_path = plugin_path.lstrip('./') + full_plugin_path = f"workspaces/{ws_name}/{clean_path}" + else: + full_plugin_path = plugin_path + + details = get_plugin_details(repo_url, commit_sha, full_plugin_path) # Check support status support_status = check_support_status(plugin_path, ws_name, supported_plugins, community_plugins) From c1f80e6235ff2165b864452abd5dfa97f8d89427 Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Thu, 20 Nov 2025 09:39:32 +0100 Subject: [PATCH 30/33] Remove feature branch from workflow triggers --- .github/workflows/update-wiki.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index 5ef0d13d..ba08c992 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -5,7 +5,6 @@ on: branches: - main - 'release-*' - - 'feature/RHIDP-9450' permissions: contents: write From bd8e5136bdfd8366c09f1b93e263c65919ac4398 Mon Sep 17 00:00:00 2001 From: rhdh-bot Date: Mon, 24 Nov 2025 16:51:37 +0100 Subject: [PATCH 31/33] JS rework --- .github/scripts/generate-wiki-page.js | 676 ++++++++++++++++++++++++++ .github/scripts/generate-wiki-page.py | 2 +- .github/workflows/update-wiki.yml | 14 +- 3 files changed, 685 insertions(+), 7 deletions(-) create mode 100644 .github/scripts/generate-wiki-page.js diff --git a/.github/scripts/generate-wiki-page.js b/.github/scripts/generate-wiki-page.js new file mode 100644 index 00000000..98fffc71 --- /dev/null +++ b/.github/scripts/generate-wiki-page.js @@ -0,0 +1,676 @@ +#!/usr/bin/env node +// @ts-check + +import { promises as fs } from 'fs'; +import { join } from 'path'; +import { execSync } from 'child_process'; +import { load } from 'js-yaml'; + +function runCommand(cmd, check = true) { + try { + const stdout = execSync(cmd.join(' '), { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'] + }); + return { exitCode: 0, stdout: stdout.trim(), stderr: '' }; + } catch (error) { + const exitCode = error.status || 1; + const stderr = error.stderr?.toString() || error.message || ''; + if (check && exitCode !== 0) { + console.error(`Command failed: ${cmd.join(' ')}`, process.stderr); + console.error(`Exit code: ${exitCode}`, process.stderr); + console.error(`Stderr: ${stderr}`, process.stderr); + } + return { + exitCode, + stdout: error.stdout?.toString().trim() || '', + stderr: stderr.trim() + }; + } +} + +async function getWorkspaceList(workspacesDir) { + try { + const entries = await fs.readdir(workspacesDir, { withFileTypes: true }); + return entries + .filter(entry => entry.isDirectory() && !entry.name.startsWith('.')) + .map(entry => entry.name) + .sort(); + } catch (error) { + console.error(`Error reading workspaces directory ${workspacesDir}: ${error.message}`, process.stderr); + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + return []; + } +} + +async function parseSourceJson(workspacePath) { + const sourceFile = join(workspacePath, 'source.json'); + try { + const content = await fs.readFile(sourceFile, 'utf-8'); + return JSON.parse(content); + } catch (error) { + if (error.code !== 'ENOENT') { + console.error(`Error reading ${sourceFile}: ${error.message}`, process.stderr); + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + if (error.stack) { + console.error(`Stack trace: ${error.stack}`, process.stderr); + } + } + return null; + } +} + +async function parsePluginsList(workspacePath) { + const pluginsFile = join(workspacePath, 'plugins-list.yaml'); + try { + const content = await fs.readFile(pluginsFile, 'utf-8'); + const trimmed = content.trim(); + if (!trimmed) { + return []; + } + + const data = load(trimmed); + if (typeof data === 'object' && data !== null) { + if (Array.isArray(data)) { + return data; + } else if (typeof data === 'object') { + return Object.keys(data); + } + } + return []; + } catch (error) { + if (error.code !== 'ENOENT') { + console.error(`Error reading ${pluginsFile}: ${error.message}`, process.stderr); + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + if (error.stack) { + console.error(`Stack trace: ${error.stack}`, process.stderr); + } + } + return []; + } +} + +async function getPluginDetails(octokit, repoUrl, commitSha, pluginPath) { + if (!repoUrl.startsWith('https://github.com/')) { + return pluginPath; + } + + const repoName = repoUrl.replace('https://github.com/', '').replace(/\/$/, ''); + const [owner, repo] = repoName.split('/'); + const filePath = `${pluginPath}/package.json`; + + try { + const response = await octokit.rest.repos.getContent({ + owner, + repo, + path: filePath, + ref: commitSha + }); + + if ('content' in response.data && response.data.encoding === 'base64') { + const content = Buffer.from(response.data.content, 'base64').toString('utf-8'); + const packageJson = JSON.parse(content); + const name = packageJson.name || 'unknown'; + const version = packageJson.version || 'unknown'; + return `${name}@${version}`; + } + } catch (error) { + console.error(`Error fetching package.json for ${pluginPath} in ${repoName}@${commitSha}: ${error.message}`, process.stderr); + if (error.status) { + console.error(`HTTP status: ${error.status}`, process.stderr); + } + if (error.response?.data) { + console.error(`Response: ${JSON.stringify(error.response.data)}`, process.stderr); + } + } + + return pluginPath; +} + +async function getCommitDetails(octokit, repoUrl, commitSha) { + if (!repoUrl.startsWith('https://github.com/')) { + return { + shortSha: commitSha.substring(0, 7), + message: 'N/A', + date: 'N/A' + }; + } + + const repoName = repoUrl.replace('https://github.com/', '').replace(/\/$/, ''); + const [owner, repo] = repoName.split('/'); + + try { + const response = await octokit.rest.repos.getCommit({ + owner, + repo, + ref: commitSha + }); + + const commit = response.data.commit; + const message = commit.message.split('\n')[0]; + const dateStr = commit.author?.date || ''; + + let formattedDate = 'N/A'; + if (dateStr) { + try { + const dt = new Date(dateStr); + formattedDate = dt.toISOString().replace('T', ' ').substring(0, 16) + ' UTC'; + } catch (dateError) { + console.error(`Error formatting date "${dateStr}": ${dateError.message}`, process.stderr); + formattedDate = dateStr; + } + } + + return { + shortSha: response.data.sha.substring(0, 7), + message, + date: formattedDate + }; + } catch (error) { + console.error(`Error fetching commit details for ${repoName}@${commitSha}: ${error.message}`, process.stderr); + if (error.status) { + console.error(`HTTP status: ${error.status}`, process.stderr); + } + if (error.response?.data) { + console.error(`Response: ${JSON.stringify(error.response.data)}`, process.stderr); + } + return { + shortSha: commitSha.substring(0, 7), + message: 'N/A', + date: 'N/A' + }; + } +} + +async function checkPendingPRs(octokit, workspaceName, repoName, targetBranch) { + const workspacePath = `workspaces/${workspaceName}`; + const [owner, repo] = repoName.split('/'); + + try { + const response = await octokit.rest.pulls.list({ + owner, + repo, + base: targetBranch, + state: 'open', + per_page: 100 + }); + + const prNumbers = []; + for (const pr of response.data) { + try { + const filesResponse = await octokit.rest.pulls.listFiles({ + owner, + repo, + pull_number: pr.number + }); + + const hasWorkspaceFile = filesResponse.data.some(file => + file.filename.startsWith(workspacePath) + ); + + const hasRequiredLabel = pr.labels?.some(label => + label.name === 'workspace_addition' || label.name === 'workspace_update' + ); + + if (hasWorkspaceFile && hasRequiredLabel) { + prNumbers.push(pr.number.toString()); + } + } catch (error) { + console.error(`Error checking files for PR #${pr.number}: ${error.message}`, process.stderr); + if (error.status) { + console.error(`HTTP status: ${error.status}`, process.stderr); + } + continue; + } + } + + return { + hasPending: prNumbers.length > 0, + prNumbers + }; + } catch (error) { + console.error(`Error checking pending PRs for workspace ${workspaceName} in ${repoName}: ${error.message}`, process.stderr); + if (error.status) { + console.error(`HTTP status: ${error.status}`, process.stderr); + } + if (error.response?.data) { + console.error(`Response: ${JSON.stringify(error.response.data)}`, process.stderr); + } + return { hasPending: false, prNumbers: [] }; + } +} + +async function getBackstageVersion(workspacePath) { + const backstageFile = join(workspacePath, 'backstage.json'); + try { + const content = await fs.readFile(backstageFile, 'utf-8'); + const data = JSON.parse(content); + if (data.version) { + return data.version; + } + } catch (error) { + if (error.code !== 'ENOENT') { + console.error(`Error reading backstage.json from ${workspacePath}: ${error.message}`, process.stderr); + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + } + } + + const sourceData = await parseSourceJson(workspacePath); + if (sourceData && sourceData['repo-backstage-version']) { + return sourceData['repo-backstage-version']; + } + + return null; +} + +async function getSourceBackstageVersion(octokit, repoUrl, commitSha) { + if (!repoUrl || !commitSha || !repoUrl.startsWith('https://github.com/')) { + return null; + } + + const repoName = repoUrl.replace('https://github.com/', '').replace(/\/$/, ''); + const [owner, repo] = repoName.split('/'); + + try { + const response = await octokit.rest.repos.getContent({ + owner, + repo, + path: 'backstage.json', + ref: commitSha + }); + + if ('content' in response.data && response.data.encoding === 'base64') { + const content = Buffer.from(response.data.content, 'base64').toString('utf-8'); + const data = JSON.parse(content); + return data.version || null; + } + } catch (error) { + console.error(`Error fetching upstream backstage.json for ${repoUrl}@${commitSha}: ${error.message}`, process.stderr); + if (error.status) { + console.error(`HTTP status: ${error.status}`, process.stderr); + } + if (error.response?.data) { + console.error(`Response: ${JSON.stringify(error.response.data)}`, process.stderr); + } + } + + return null; +} + +async function loadPluginLists() { + const supported = []; + const community = []; + + try { + const supportedPath = 'rhdh-supported-plugins.txt'; + try { + const content = await fs.readFile(supportedPath, 'utf-8'); + supported.push(...content + .split('\n') + .map(line => line.trim()) + .filter(line => line && !line.startsWith('#')) + ); + } catch (error) { + if (error.code !== 'ENOENT') { + console.error(`Error reading ${supportedPath}: ${error.message}`, process.stderr); + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + } + } + + const communityPath = 'rhdh-community-plugins.txt'; + try { + const content = await fs.readFile(communityPath, 'utf-8'); + community.push(...content + .split('\n') + .map(line => line.trim()) + .filter(line => line && !line.startsWith('#')) + ); + } catch (error) { + if (error.code !== 'ENOENT') { + console.error(`Error reading ${communityPath}: ${error.message}`, process.stderr); + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + } + } + } catch (error) { + console.error(`Error loading plugin lists: ${error.message}`, process.stderr); + if (error.stack) { + console.error(`Stack trace: ${error.stack}`, process.stderr); + } + } + + return { supported, community }; +} + +function checkSupportStatus(pluginPath, workspaceName, supportedList, communityList) { + const cleanPluginPath = pluginPath.replace(/^\.?\//, '').replace(/^\//, ''); + const fullPath = `${workspaceName}/${cleanPluginPath}`; + + if (supportedList.includes(fullPath)) { + return 'Supported'; + } + if (communityList.includes(fullPath)) { + return 'Community'; + } + + if (supportedList.includes(cleanPluginPath)) { + return 'Supported'; + } + if (communityList.includes(cleanPluginPath)) { + return 'Community'; + } + + return 'Unknown'; +} + +async function countFilesRecursive(dirPath) { + let count = 0; + try { + const entries = await fs.readdir(dirPath, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = join(dirPath, entry.name); + if (entry.isFile()) { + count++; + } else if (entry.isDirectory()) { + count += await countFilesRecursive(fullPath); + } + } + } catch (error) { + console.error(`Error counting files in ${dirPath}: ${error.message}`, process.stderr); + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + } + return count; +} + +async function countAdditionalFiles(workspacePath) { + const counts = { + metadata: 0, + plugins: 0, + patches: 0, + tests: 0 + }; + + for (const key of Object.keys(counts)) { + const dirPath = join(workspacePath, key); + try { + const stat = await fs.stat(dirPath); + if (stat.isDirectory()) { + counts[key] = await countFilesRecursive(dirPath); + } + } catch (error) { + if (error.code !== 'ENOENT') { + console.error(`Error counting files in ${dirPath}: ${error.message}`, process.stderr); + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + } + } + } + + return counts; +} + +function generateMarkdown(branchName, workspacesData, repoName) { + const md = []; + + md.push(`# Workspace Status: \`${branchName}\``); + md.push(''); + const now = new Date(); + const utcDate = now.toISOString().replace('T', ' ').substring(0, 16) + ' UTC'; + md.push(`**Last Updated:** ${utcDate}`); + md.push(''); + md.push(`**Total Workspaces:** ${workspacesData.length}`); + md.push(''); + md.push('---'); + md.push(''); + + md.push('## Workspace Overview'); + md.push(''); + md.push('| Type | Workspace | Source | Backstage Version | Plugins |'); + md.push('|:----:|-----------|--------|------------------|---------|'); + + for (const ws of workspacesData) { + const sourceJsonUrl = `https://github.com/${repoName}/blob/${branchName}/workspaces/${ws.name}/source.json`; + const structIcon = ws.repo_flat ? '📄' : '🌳'; + const structTooltip = ws.repo_flat ? 'Flat (root-level plugins)' : 'Monorepo (workspace-based)'; + + const structureBadges = []; + structureBadges.push(`[${structIcon}](${sourceJsonUrl} "${structTooltip}")`); + + if (ws.additional_files.patches > 0) { + const patchesUrl = `https://github.com/${repoName}/tree/${branchName}/workspaces/${ws.name}/patches`; + structureBadges.push(`[🩹](${patchesUrl})`); + } + + if (ws.additional_files.plugins > 0) { + const pluginsUrl = `https://github.com/${repoName}/tree/${branchName}/workspaces/${ws.name}/plugins`; + structureBadges.push(`[🔄](${pluginsUrl})`); + } + + if (ws.additional_files.metadata > 0) { + structureBadges.push('🟢'); + } else { + structureBadges.push('🔴'); + } + + if (ws.has_pending_prs && ws.pr_numbers.length > 0) { + const prNum = ws.pr_numbers[0]; + const prUrl = `https://github.com/${repoName}/pull/${prNum}`; + const prTooltip = `Pending update PR #${prNum}`; + structureBadges.push(`[🔴](${prUrl})`); + } + + const structure = structureBadges.join('
'); + const overlayRepoUrl = `https://github.com/${repoName}/tree/${branchName}/workspaces/${ws.name}`; + const workspaceName = `[${ws.name}](${overlayRepoUrl})`; + + let source = 'N/A'; + if (ws.repo_url && ws.commit_sha) { + const repoNameOnly = ws.repo_url.replace('https://github.com/', ''); + if (ws.repo_flat) { + const sourceUrl = `${ws.repo_url}/tree/${ws.commit_sha}`; + source = `[${repoNameOnly}@${ws.commit_short}](${sourceUrl})`; + } else { + const workspacePath = `workspaces/${ws.name}`; + const sourceUrl = `${ws.repo_url}/tree/${ws.commit_sha}/${workspacePath}`; + source = `[${repoNameOnly}@${ws.commit_short}](${sourceUrl})`; + } + } + + const overlayVersion = ws.overlay_backstage_version; + const sourceVersion = ws.source_backstage_version; + const displayVersion = sourceVersion || overlayVersion; + + let backstageVersion = 'N/A'; + if (displayVersion) { + if (overlayVersion && sourceVersion && overlayVersion !== sourceVersion) { + const tooltip = `Overlay overrides upstream version ${sourceVersion} to ${overlayVersion}`.replace(/"/g, '"'); + backstageVersion = `\`${displayVersion}\` ⚠️`; + } else { + backstageVersion = `\`${displayVersion}\``; + } + } + + let pluginsList = 'No plugins'; + if (ws.plugins && ws.plugins.length > 0) { + const pluginsListItems = ws.plugins.map(p => { + const nameVer = p.details; + const status = p.status; + + let icon, tooltip; + if (status === 'Supported') { + icon = '🟢'; + tooltip = 'Red Hat Supported'; + } else if (status === 'Community') { + icon = '🟡'; + tooltip = 'Community / Dev Preview'; + } else { + icon = '▪️'; + tooltip = 'Community / Unknown'; + } + + return `${icon} \`${nameVer}\``; + }); + + pluginsList = pluginsListItems.join('
'); + } + + md.push(`| ${structure} | ${workspaceName} | ${source} | ${backstageVersion} | ${pluginsList} |`); + } + + md.push(''); + md.push('---'); + md.push(''); + + return md.join('\n'); +} + +async function main() { + // @ts-ignore + const { Octokit } = require('@octokit/rest'); + + const branchName = process.env.BRANCH_NAME || 'main'; + const repoName = process.env.REPO_NAME || 'unknown/unknown'; + const ghToken = process.env.GH_TOKEN || ''; + + console.log(`Generating wiki page for branch: ${branchName}`); + console.log(`Repository: ${repoName}`); + + const octokit = new Octokit({ + auth: ghToken + }); + + const workspacesDir = 'workspaces'; + const workspaceNames = await getWorkspaceList(workspacesDir); + console.log(`Found ${workspaceNames.length} workspaces`); + + const { supported: supportedPlugins, community: communityPlugins } = await loadPluginLists(); + console.log(`Loaded ${supportedPlugins.length} supported and ${communityPlugins.length} community plugins`); + + const workspacesData = []; + + for (const wsName of workspaceNames) { + console.log(`Processing workspace: ${wsName}`); + const wsPath = join(workspacesDir, wsName); + + const sourceData = await parseSourceJson(wsPath); + const plugins = await parsePluginsList(wsPath); + + let commitSha = null; + let commitShort = null; + let commitMessage = 'N/A'; + let commitDate = 'N/A'; + let repoUrl = null; + let repoFlat = false; + + if (sourceData) { + repoUrl = sourceData.repo || null; + commitSha = sourceData['repo-ref'] || null; + repoFlat = sourceData['repo-flat'] || false; + + if (repoUrl && commitSha) { + const commitDetails = await getCommitDetails(octokit, repoUrl, commitSha); + commitShort = commitDetails.shortSha; + commitMessage = commitDetails.message; + commitDate = commitDetails.date; + } + } + + const backstageVersion = await getBackstageVersion(wsPath); + const sourceBackstageVersion = repoUrl && commitSha + ? await getSourceBackstageVersion(octokit, repoUrl, commitSha) + : null; + + const enhancedPlugins = []; + if (repoUrl && commitSha) { + console.log(` Fetching plugin details for ${plugins.length} plugins...`); + for (const pluginPath of plugins) { + const cleanPath = pluginPath.replace(/^\.?\//, ''); + const fullPluginPath = repoFlat + ? cleanPath + : `workspaces/${wsName}/${cleanPath}`; + + const details = await getPluginDetails(octokit, repoUrl, commitSha, fullPluginPath); + const supportStatus = checkSupportStatus(pluginPath, wsName, supportedPlugins, communityPlugins); + + enhancedPlugins.push({ + details, + path: pluginPath, + status: supportStatus + }); + } + } else { + for (const pluginPath of plugins) { + const supportStatus = checkSupportStatus(pluginPath, wsName, supportedPlugins, communityPlugins); + enhancedPlugins.push({ + details: pluginPath, + path: pluginPath, + status: supportStatus + }); + } + } + + const additionalFiles = await countAdditionalFiles(wsPath); + const { hasPending: hasPendingPRs, prNumbers } = await checkPendingPRs( + octokit, + wsName, + repoName, + branchName + ); + + workspacesData.push({ + name: wsName, + repo_url: repoUrl, + commit_sha: commitSha, + commit_short: commitShort, + commit_message: commitMessage, + commit_date: commitDate, + repo_flat: repoFlat, + overlay_backstage_version: backstageVersion, + source_backstage_version: sourceBackstageVersion, + plugins: enhancedPlugins, + additional_files: additionalFiles, + has_pending_prs: hasPendingPRs, + pr_numbers: prNumbers + }); + } + + console.log('Generating Markdown content...'); + const markdownContent = generateMarkdown(branchName, workspacesData, repoName); + + const safeBranchName = branchName.replace(/\//g, '-'); + const outputFile = `${safeBranchName}.md`; + await fs.writeFile(outputFile, markdownContent, 'utf-8'); + + console.log(`Wiki page generated: ${outputFile}`); + console.log(`Total workspaces documented: ${workspacesData.length}`); +} + +if (require.main === module) { + main().catch(error => { + console.error('Fatal error in main:', error.message, process.stderr); + if (error.stack) { + console.error(`Stack trace: ${error.stack}`, process.stderr); + } + if (error.code) { + console.error(`Error code: ${error.code}`, process.stderr); + } + process.exit(1); + }); +} + +export default { main }; + diff --git a/.github/scripts/generate-wiki-page.py b/.github/scripts/generate-wiki-page.py index 721d6be9..45dd3a5d 100755 --- a/.github/scripts/generate-wiki-page.py +++ b/.github/scripts/generate-wiki-page.py @@ -165,7 +165,7 @@ def check_pending_prs(workspace_name: str, repo_name: str, target_branch: str) - Filters by: - Base branch (target_branch) - Files touching the workspace path - - Labels: workspace_addition or workspace_update + - Labels: workspace_update Returns (has_pending, [pr_numbers]) """ workspace_path = f"workspaces/{workspace_name}" diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml index ba08c992..26adb75b 100644 --- a/.github/workflows/update-wiki.yml +++ b/.github/workflows/update-wiki.yml @@ -5,6 +5,7 @@ on: branches: - main - 'release-*' + - 'feature/RHIDP-9450' permissions: contents: write @@ -15,16 +16,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Set up Node.js + uses: actions/setup-node@v6 with: - python-version: '3.11' + node-version: '24' + - name: Install dependencies run: | - pip install pyyaml requests + npm install @octokit/rest js-yaml - name: Get branch name id: branch @@ -42,7 +44,7 @@ jobs: BRANCH_NAME: ${{ steps.branch.outputs.name }} REPO_NAME: redhat-developer/rhdh-plugin-export-overlays run: | - python .github/scripts/generate-wiki-page.py + node .github/scripts/generate-wiki-page.js - name: Checkout wiki repository uses: actions/checkout@v4 From 27a54bc4aa6645cbb5927559368d3db373a246cd Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Mon, 24 Nov 2025 17:04:47 +0100 Subject: [PATCH 32/33] Convert JavaScript script to ES modules syntax - Replace CommonJS require() with ES module imports - Update module detection for ES modules - Add proper import statements for dependencies --- .github/scripts/generate-wiki-page.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/scripts/generate-wiki-page.js b/.github/scripts/generate-wiki-page.js index 98fffc71..84293824 100644 --- a/.github/scripts/generate-wiki-page.js +++ b/.github/scripts/generate-wiki-page.js @@ -5,6 +5,9 @@ import { promises as fs } from 'fs'; import { join } from 'path'; import { execSync } from 'child_process'; import { load } from 'js-yaml'; +// @ts-ignore +import { Octokit } from '@octokit/rest'; +import { fileURLToPath } from 'url'; function runCommand(cmd, check = true) { try { @@ -539,9 +542,6 @@ function generateMarkdown(branchName, workspacesData, repoName) { } async function main() { - // @ts-ignore - const { Octokit } = require('@octokit/rest'); - const branchName = process.env.BRANCH_NAME || 'main'; const repoName = process.env.REPO_NAME || 'unknown/unknown'; const ghToken = process.env.GH_TOKEN || ''; @@ -659,7 +659,7 @@ async function main() { console.log(`Total workspaces documented: ${workspacesData.length}`); } -if (require.main === module) { +if (process.argv[1] === fileURLToPath(import.meta.url)) { main().catch(error => { console.error('Fatal error in main:', error.message, process.stderr); if (error.stack) { @@ -673,4 +673,3 @@ if (require.main === module) { } export default { main }; - From 663ed214ff7b380853cec7440383c3d048ddd28b Mon Sep 17 00:00:00 2001 From: Martin Polasko Date: Mon, 24 Nov 2025 17:11:17 +0100 Subject: [PATCH 33/33] Update package list filenames - Change from rhdh-supported-plugins.txt to rhdh-supported-packages.txt - Change from rhdh-community-plugins.txt to rhdh-community-packages.txt --- .github/scripts/generate-wiki-page.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/generate-wiki-page.js b/.github/scripts/generate-wiki-page.js index 84293824..81eced48 100644 --- a/.github/scripts/generate-wiki-page.js +++ b/.github/scripts/generate-wiki-page.js @@ -313,7 +313,7 @@ async function loadPluginLists() { const community = []; try { - const supportedPath = 'rhdh-supported-plugins.txt'; + const supportedPath = 'rhdh-supported-packages.txt'; try { const content = await fs.readFile(supportedPath, 'utf-8'); supported.push(...content @@ -330,7 +330,7 @@ async function loadPluginLists() { } } - const communityPath = 'rhdh-community-plugins.txt'; + const communityPath = 'rhdh-community-packages.txt'; try { const content = await fs.readFile(communityPath, 'utf-8'); community.push(...content