name: TEST publish-version-xml (local only) on: workflow_dispatch: jobs: publish-version-xml: name: Publish Version Info XML via WebDAV runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v7 - name: Generate kst4ContestVersionInfo.xml from GitHub releases + history env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | python3 << 'PYEOF' import subprocess, json from xml.etree.ElementTree import Element, SubElement, ElementTree, parse, indent def safe_version(tag): cleaned = tag.lstrip('v') try: float(cleaned) except ValueError: pass return cleaned import os, urllib.request REPO = 'praktimarc/kst4contest' token = os.environ['GH_TOKEN'] def gh_get(path): url = f'https://api.github.com/repos/{REPO}{path}' req = urllib.request.Request(url, headers={ 'Authorization': f'Bearer {token}', 'Accept': 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28', }) with urllib.request.urlopen(req) as resp: return json.loads(resp.read()) def get_issues_for_release(since, until): """Return closed issues in (since, until] categorised by label.""" LABEL_MAP = {'enhancement': 'added', 'bug': 'fixed'} buckets = {'added': [], 'fixed': []} page = 1 while True: items = gh_get(f'/issues?state=closed&per_page=100&page={page}') if not items: break for issue in items: if 'pull_request' in issue: continue closed = issue.get('closed_at') or '' if not (since < closed <= until): continue labels = [l['name'] for l in issue['labels']] bucket = next((LABEL_MAP[l] for l in labels if l in LABEL_MAP), None) if bucket: buckets[bucket].append(f'- {issue["title"]}') if len(items) < 100: break page += 1 return {k: '\n'.join(v) for k, v in buckets.items()} def clean_body(body): """Strip GitHub markdown noise, keep plain text.""" import re lines = [] for line in (body or '').splitlines(): if re.match(r'^#{1,3}\s', line): continue if line.strip().startswith('**Full Changelog**'): continue line = re.sub(r'\s+by @\S+ in https://\S+', '', line) line = re.sub(r'^\* ', '- ', line) if line.strip(): lines.append(line) return re.sub(r'\n{3,}', '\n\n', '\n'.join(lines)).strip() # --- Fetch all releases --- raw = gh_get('/releases?per_page=100') gh_releases = [ { 'tagName': r['tag_name'], 'publishedAt': r['published_at'] or '', 'name': r['name'] or r['tag_name'], 'body': r['body'] or '', 'isPrerelease': r['prerelease'], } for r in raw ] # --- Simulate a stable 1.41 release for testing --- gh_releases.insert(0, { 'tagName': '1.41', 'publishedAt': '2026-06-30T10:00:00Z', 'name': 'Release 1.41', 'body': 'Release 1.41', 'isPrerelease': False, }) gh_releases.sort(key=lambda r: r['publishedAt'], reverse=True) stable_releases = [r for r in gh_releases if not r['isPrerelease']] stable = stable_releases[0] if stable_releases else None gh_versions = {safe_version(r['tagName']) for r in stable_releases} history_tree = parse('.github/version-history.xml') history_entries = history_tree.getroot().findall('changeLog') root = Element('praktiKST') lv = SubElement(root, 'latestVersion') latest_tag = stable['tagName'] if stable else 'unknown' SubElement(lv, 'versionNumber').text = safe_version(latest_tag) SubElement(lv, 'adminMessage').text = '' latest_issues = get_issues_for_release('1970-01-01T00:00:00Z', stable['publishedAt']) if stable else {} SubElement(lv, 'majorChanges').text = latest_issues.get('added', '')[:300] win_filename = f'praktiKST-{latest_tag}-windows-x64.zip' SubElement(lv, 'latestVersionPathOnWebserver').text = ( f'https://github.com/{REPO}/releases/download/{latest_tag}/{win_filename}' ) nuf = SubElement(root, 'needUpdateSinceLastVersion') SubElement(nuf, 'filename').text = win_filename # Build changelog for each stable release using closed issues in its window for i, rel in enumerate(stable_releases): until = rel['publishedAt'] since = stable_releases[i + 1]['publishedAt'] if i + 1 < len(stable_releases) else '1970-01-01T00:00:00Z' s = get_issues_for_release(since, until) cl = SubElement(root, 'changeLog') SubElement(cl, 'changedVersionNumber').text = safe_version(rel['tagName']) SubElement(cl, 'date').text = until[:10] SubElement(cl, 'description').text = rel['name'] SubElement(cl, 'added').text = s['added'] SubElement(cl, 'changed').text = clean_body(rel['body']) SubElement(cl, 'fixed').text = s['fixed'] SubElement(cl, 'removed').text = '' for entry in history_entries: ver_node = entry.find('changedVersionNumber') if ver_node is not None and ver_node.text and ver_node.text.strip() in gh_versions: continue root.append(entry) indent(root, space=' ') tree = ElementTree(root) with open('kst4ContestVersionInfo.xml', 'w', encoding='utf-8') as f: f.write('\n') tree.write(f, encoding='unicode', xml_declaration=False) print('Generated kst4ContestVersionInfo.xml') PYEOF - name: Show generated XML (first 40 lines) run: head -40 kst4ContestVersionInfo.xml - name: Upload to WebDAV run: | curl --fail --silent --show-error \ --user "${{ secrets.WEBDAV_USER }}:${{ secrets.WEBDAV_PASSWORD }}" \ --upload-file kst4ContestVersionInfo.xml \ "${{ secrets.WEBDAV_URL }}/kst4ContestVersionInfo.xml"