From 5718669a5487d8c1d82c5788ed573c8e119c709e Mon Sep 17 00:00:00 2001 From: DeMiro5001 <68397534+DeMiro5001@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:54:11 +0100 Subject: [PATCH 1/4] Create check-config.yml --- .github/workflows/check-config.yml | 193 +++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 .github/workflows/check-config.yml diff --git a/.github/workflows/check-config.yml b/.github/workflows/check-config.yml new file mode 100644 index 0000000..e3715c2 --- /dev/null +++ b/.github/workflows/check-config.yml @@ -0,0 +1,193 @@ +name: Check Upstream Config Changes + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +jobs: + compare-configs: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install Packaging Lib + run: pip install packaging + + - name: Determine Latest OJS Version + id: get_version + run: | + LATEST_TAG=$(curl -sL "https://api.github.com/repos/pkp/ojs/tags?per_page=100" \ + | python3 -c " + import sys, json, re + from packaging.version import parse + try: + tags = json.load(sys.stdin) + valid_tags = [] + tag_map = {} + for t in tags: + name = t['name'] + m = re.search(r'(?:ojs-)?(3[._]\d+[._]\d+[-_]\d+)', name) + if m: + version_str = m.group(1).replace('_', '.').replace('-', '.') + valid_tags.append(version_str) + tag_map[version_str] = name + if not valid_tags: + print('stable-3_4_0') + sys.exit(0) + valid_tags.sort(key=parse, reverse=True) + print(tag_map[valid_tags[0]]) + except Exception: + print('stable-3_4_0') + ") + echo "Detected latest tag: $LATEST_TAG" + echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT + + - name: Fetch Upstream Config + env: + TAG: ${{ steps.get_version.outputs.tag }} + run: | + echo "Downloading config template for tag: $TAG" + curl -sSL "https://raw.githubusercontent.com/pkp/ojs/$TAG/config.TEMPLATE.inc.php" -o upstream.config.php + + - name: Run Comparison Script + id: check_diff + shell: python + env: + TAG: ${{ steps.get_version.outputs.tag }} + run: | + import configparser + import sys + import os + import re + + # --- CONFIGURATION --- + LOCAL_FILE = 'conf/config.inc.php' + UPSTREAM_FILE = 'upstream.config.php' + IGNORED_SECTIONS = [] + # --------------------- + + def load_ini(filepath): + config = configparser.ConfigParser(strict=False, allow_no_value=True) + config.optionxform = str + try: + config.read(filepath) + except Exception as e: + print(f"Error reading {filepath}: {e}") + sys.exit(1) + return config + + if not os.path.exists(LOCAL_FILE): + print(f"::error::Local file {LOCAL_FILE} not found!") + sys.exit(1) + + local_cfg = load_ini(LOCAL_FILE) + upstream_cfg = load_ini(UPSTREAM_FILE) + + # Read raw upstream content for regex checking + with open(UPSTREAM_FILE, 'r') as f: + upstream_raw = f.read() + + missing_sections = [] + missing_keys = [] + deprecated_keys = [] + + # 1. Check for NEW items (Upstream has it, Local doesn't) + for section in upstream_cfg.sections(): + if section in IGNORED_SECTIONS: continue + + if not local_cfg.has_section(section): + missing_sections.append(section) + continue + + for key in upstream_cfg.options(section): + if not local_cfg.has_option(section, key): + missing_keys.append(f"[{section}] {key}") + + # 2. Check for DEPRECATED items (Local has it, Upstream doesn't) + for section in local_cfg.sections(): + if section in IGNORED_SECTIONS: continue + + # Helper to check if key exists in raw file (commented or not) + def key_exists_in_raw(key_name): + # Regex looks for: start of line, optional space, optional semicolon, key, equals + # e.g. matches "; smtp_server =" or "smtp_server =" + pattern = r"^\s*;?\s*" + re.escape(key_name) + r"\s*=" + return re.search(pattern, upstream_raw, re.MULTILINE) is not None + + if not upstream_cfg.has_section(section): + # Even if section is missing from active config, check raw text + # (Though typically if section is gone, keys are gone too, but let's be safe) + for key in local_cfg.options(section): + if not key_exists_in_raw(key): + deprecated_keys.append(f"[{section}] {key} (Section removed)") + continue + + for key in local_cfg.options(section): + # If key is not in active upstream config... + if not upstream_cfg.has_option(section, key): + # ...CHECK RAW TEXT before declaring it deprecated + if not key_exists_in_raw(key): + deprecated_keys.append(f"[{section}] {key}") + + # 3. Generate Report + if missing_sections or missing_keys or deprecated_keys: + tag_name = os.environ.get('TAG', 'unknown') + report = f"### ℹ️ Upstream Config Changes Detected ({tag_name})\n\n" + + if missing_sections or missing_keys: + report += "**Missing Keys (New in Upstream):**\n" + if missing_sections: + for s in missing_sections: report += f"- `[{s}]` (Entire Section)\n" + for k in missing_keys: + report += f"- `{k}`\n" + report += "\n" + + if deprecated_keys: + report += "**Deprecated Keys (Removed from Upstream):**\n" + report += "> *These keys exist in your local file but seem to be completely removed from the upstream template.*\n" + for k in deprecated_keys: + report += f"- `{k}`\n" + + delimiter = "EOF" + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write(f"comment<<{delimiter}\n{report}\n{delimiter}\n") + f.write("has_changes=true\n") + else: + print("Configs match perfectly.") + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write("has_changes=false\n") + + - name: Post Comment on PR + if: steps.check_diff.outputs.has_changes == 'true' + uses: actions/github-script@v6 + env: + COMMENT_BODY: ${{ steps.check_diff.outputs.comment }} + with: + script: | + const output = process.env.COMMENT_BODY; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const botComment = comments.find(c => c.body.includes('Upstream Config Changes Detected')); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, repo: context.repo.repo, comment_id: botComment.id, body: output + }); + } else { + await github.rest.issues.createComment({ + issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: output + }); + } From 03ec2de236eb9c3b4038ffa8f67be34b679b1baf Mon Sep 17 00:00:00 2001 From: DeMiro5001 <68397534+DeMiro5001@users.noreply.github.com> Date: Sat, 10 Jan 2026 10:36:39 +0100 Subject: [PATCH 2/4] Update check-config.yml --- .github/workflows/check-config.yml | 110 +++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 29 deletions(-) diff --git a/.github/workflows/check-config.yml b/.github/workflows/check-config.yml index e3715c2..23ea774 100644 --- a/.github/workflows/check-config.yml +++ b/.github/workflows/check-config.yml @@ -93,65 +93,117 @@ jobs: local_cfg = load_ini(LOCAL_FILE) upstream_cfg = load_ini(UPSTREAM_FILE) - # Read raw upstream content for regex checking + # Read raw upstream content for context extraction with open(UPSTREAM_FILE, 'r') as f: - upstream_raw = f.read() + upstream_lines = f.readlines() + upstream_raw = "".join(upstream_lines) + + # --- HELPER: Extract comments and raw line for a key --- + def get_key_context(target_section, target_key): + current_section = None + for i, line in enumerate(upstream_lines): + stripped = line.strip() + # Detect section header + if stripped.startswith('[') and stripped.endswith(']'): + current_section = stripped[1:-1] + continue + + # If we are in the right section, look for the key + if current_section == target_section: + # Regex: Start of line, optional whitespace, key, whitespace, equals + if re.match(r'^\s*' + re.escape(target_key) + r'\s*=', line): + # Found the key line! Now look backwards for comments. + comments = [] + j = i - 1 + while j >= 0: + prev = upstream_lines[j] + if prev.strip().startswith(';'): + comments.insert(0, prev.rstrip()) + elif not prev.strip(): + # Stop at blank line (end of comment block) + break + else: + # Stop if we hit another key or section + break + j -= 1 + + # Return (list of comment lines, the raw key line) + return comments, line.rstrip() + return [], f"{target_key} =" missing_sections = [] - missing_keys = [] + # Dictionary to hold missing keys grouped by section: { 'section': [text_block, text_block] } + missing_keys_grouped = {} deprecated_keys = [] - # 1. Check for NEW items (Upstream has it, Local doesn't) + # 1. Check for NEW items for section in upstream_cfg.sections(): if section in IGNORED_SECTIONS: continue if not local_cfg.has_section(section): missing_sections.append(section) + # If whole section is missing, we could list all keys, + # but usually just flagging the section is enough. continue for key in upstream_cfg.options(section): if not local_cfg.has_option(section, key): - missing_keys.append(f"[{section}] {key}") + # Extract the raw context + comments, raw_line = get_key_context(section, key) + + # Format the block + block = "" + for c in comments: + block += f"{c}\n" + block += f"{raw_line}" + + if section not in missing_keys_grouped: + missing_keys_grouped[section] = [] + missing_keys_grouped[section].append(block) + + # 2. Check for DEPRECATED items + # Helper for raw check + def key_exists_in_raw(key_name): + pattern = r"^\s*;?\s*" + re.escape(key_name) + r"\s*=" + return re.search(pattern, upstream_raw, re.MULTILINE) is not None - # 2. Check for DEPRECATED items (Local has it, Upstream doesn't) for section in local_cfg.sections(): if section in IGNORED_SECTIONS: continue - # Helper to check if key exists in raw file (commented or not) - def key_exists_in_raw(key_name): - # Regex looks for: start of line, optional space, optional semicolon, key, equals - # e.g. matches "; smtp_server =" or "smtp_server =" - pattern = r"^\s*;?\s*" + re.escape(key_name) + r"\s*=" - return re.search(pattern, upstream_raw, re.MULTILINE) is not None - if not upstream_cfg.has_section(section): - # Even if section is missing from active config, check raw text - # (Though typically if section is gone, keys are gone too, but let's be safe) - for key in local_cfg.options(section): - if not key_exists_in_raw(key): - deprecated_keys.append(f"[{section}] {key} (Section removed)") - continue + # Check if section header exists in raw even if commented out + if f"[{section}]" not in upstream_raw: + for key in local_cfg.options(section): + deprecated_keys.append(f"[{section}] {key} (Section removed)") + continue for key in local_cfg.options(section): - # If key is not in active upstream config... if not upstream_cfg.has_option(section, key): - # ...CHECK RAW TEXT before declaring it deprecated if not key_exists_in_raw(key): deprecated_keys.append(f"[{section}] {key}") # 3. Generate Report - if missing_sections or missing_keys or deprecated_keys: + if missing_sections or missing_keys_grouped or deprecated_keys: tag_name = os.environ.get('TAG', 'unknown') - report = f"### ℹ️ Upstream Config Changes Detected ({tag_name})\n\n" + report = f"### ⚠️ Upstream Config Changes Detected ({tag_name})\n\n" - if missing_sections or missing_keys: - report += "**Missing Keys (New in Upstream):**\n" - if missing_sections: - for s in missing_sections: report += f"- `[{s}]` (Entire Section)\n" - for k in missing_keys: - report += f"- `{k}`\n" + if missing_sections: + report += "**Missing Sections (New in Upstream):**\n" + for s in missing_sections: + report += f"- `[{s}]`\n" report += "\n" + if missing_keys_grouped: + report += "**Missing Keys (New in Upstream):**\n" + report += "Below are the missing keys with their original comments and default values.\n\n" + + for section, blocks in missing_keys_grouped.items(): + report += f"**Section `[{section}]`**\n" + report += "```ini\n" + for block in blocks: + report += f"{block}\n\n" + report += "```\n" + if deprecated_keys: report += "**Deprecated Keys (Removed from Upstream):**\n" report += "> *These keys exist in your local file but seem to be completely removed from the upstream template.*\n" From 2e672b2c8149954baff1ee2b9cf2f27e9e500472 Mon Sep 17 00:00:00 2001 From: DeMiro5001 <68397534+DeMiro5001@users.noreply.github.com> Date: Sat, 10 Jan 2026 11:25:36 +0100 Subject: [PATCH 3/4] Update manifest.toml --- manifest.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.toml b/manifest.toml index e39d4cd..be43b33 100644 --- a/manifest.toml +++ b/manifest.toml @@ -7,7 +7,7 @@ name = "OJS" description.en = "Software to manage scholarly journals" description.fr = "Logiciel pour gérer des revues scientifiques" -version = "3.5.0-1~ynh4" +version = "3.5.0-3~ynh1" maintainers = ["DeMiro5001"] @@ -206,8 +206,8 @@ ram.runtime = "50M" [resources.sources] [resources.sources.main] - url = "https://pkp.sfu.ca/ojs/download/ojs-3.5.0-1.tar.gz" - sha256 = "78e423421310951cfb290c3856f85aa014ff0fe3d3c02d200d6a65984951b124" + url = "https://pkp.sfu.ca/ojs/download/ojs-3.5.0-3.tar.gz" + sha256 = "af501e4f8d99af84d47c26eca3347400d94b3ace08806b5e30a7b6d0ce91e3e5" [resources.system_user] allow_email = true From 900566f05da67f679054ad840eee08cae2dba76f Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sat, 10 Jan 2026 11:25:38 +0100 Subject: [PATCH 4/4] Auto-update READMEs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b4e6ae2..82e1716 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It shall NOT be edited by hand. Software to manage scholarly journals [![🌐 Official app website](https://img.shields.io/badge/Official_app_website-darkgreen?style=for-the-badge)](https://pkp.sfu.ca/software/ojs) -[![Version: 3.5.0-1~ynh4](https://img.shields.io/badge/Version-3.5.0--1~ynh4-rgb(18,138,11)?style=for-the-badge)](https://ci-apps.yunohost.org/ci/apps/ojs/) +[![Version: 3.5.0-3~ynh1](https://img.shields.io/badge/Version-3.5.0--3~ynh1-rgb(18,138,11)?style=for-the-badge)](https://ci-apps.yunohost.org/ci/apps/ojs/)