Stop following upstream web UI releases #134

Merged
jcoffey-dev merged 1 commits from chore/stop-tracking-upstream into main 2026-10-22 22:23:23 +00:00
3 changed files with 4 additions and 155 deletions

No files matched your search

-152
View File
@@ -1,152 +0,0 @@
# Watch the upstream web UI for new versions, and open an issue for each one
# so it can be read for features worth writing here.
#
# The console no longer merges upstream (README › Upstream): later versions
# include code under a proprietary license. So the issue lists what changed,
# file by file, grouped by each file's license header, for a reader to know
# which files may be opened at all. Enterprise-only files, and files holding
# Enterprise snippets, are named but must never be opened.
#
# Reads metadata only: the tag list and the compare between two tags from
# GitHub's API. Each changed file is fetched just to read its license header
# lines; nothing of upstream's code is kept, printed, or put in the issue, only
# paths, change counts and licenses. Titles and bodies stay free of the
# upstream project's name, as the rest of the fork's user-visible text does.
#
# Each version since the fork point (v1.0.11) gets one issue, compared with
# the version before it. An existing issue with the same title, open or
# closed, stops a second. Tags are watched rather than releases: upstream has
# tagged versions before publishing them as releases.
#
# Daily 06:47 UTC; run it by hand with workflow_dispatch. DRY_RUN=1 prints
# the issues instead of opening them.
name: upstream-watch
on:
schedule:
- cron: '47 6 * * *'
workflow_dispatch:
concurrency:
group: upstream-watch
cancel-in-progress: false
jobs:
upstream-watch:
runs-on: light
container:
image: python:3.13-slim@sha256:8d9d0b8bcf6506481eae4907c18f5e3e7902e629f5f6d684f9e7c32e85e3ddf0 # 3.13-slim
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
steps:
- shell: bash
run: |
python3 - <<'PY'
import json, os, re, sys, urllib.parse, urllib.request
FORK_BASE = "v1.0.11"
UPSTREAM = "stalwartlabs/webui"
DRY_RUN = os.environ.get("DRY_RUN") == "1"
api = f"{os.environ.get('CI_SERVER_INTERNAL', '')}/api/v1/repos/{os.environ.get('REPO', '')}"
def call(method, url, body=None, token=None, raw=False):
headers = {"User-Agent": "inbuxa-admin-upstream-watch"}
if body is not None:
headers["Content-Type"] = "application/json"
if token:
headers["Authorization"] = f"token {token}"
req = urllib.request.Request(url, method=method, headers=headers,
data=json.dumps(body).encode() if body is not None else None)
with urllib.request.urlopen(req, timeout=30) as r:
return r.read() if raw else json.load(r)
SEMVER = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$")
def key(tag):
return tuple(int(x) for x in SEMVER.match(tag).groups())
def plural(n, word):
return f"{n} {word}" if n == 1 else f"{n} {word}s"
# Unauthenticated: a public repo, once a day, well inside the limit.
tags = call("GET", f"https://api.github.com/repos/{UPSTREAM}/tags?per_page=100")
newer = sorted((t["name"] for t in tags
if SEMVER.match(t["name"]) and key(t["name"]) > key(FORK_BASE)), key=key)
if not newer:
print(f"Nothing new: {FORK_BASE}, the fork point, is the newest version."); sys.exit(0)
if DRY_RUN:
existing = set()
else:
existing = {i["title"] for i in call("GET", f"{api}/issues?state=all&type=issues"
"&q=Review+upstream+web+UI&limit=50", token=os.environ["TOKEN"])}
SPDX = re.compile(r"SPDX-License-Identifier:\s*(.+?)\s*(?:\*/|-->)?\s*$", re.M)
def license_of(tag, path):
"""One of: enterprise, snippets, dual, other:<id>, none. Reads only the license lines."""
url = f"https://raw.githubusercontent.com/{UPSTREAM}/{urllib.parse.quote(tag)}/{urllib.parse.quote(path)}"
text = call("GET", url, raw=True).decode("utf-8", "replace")
ids = SPDX.findall(text)
if not ids:
return "none"
head = ids[0]
if head == "LicenseRef-SEL":
return "enterprise"
if "SPDX-SnippetBegin" in text or "LicenseRef-SEL" in ids[1:]:
return "snippets"
if head == "AGPL-3.0-only OR LicenseRef-SEL":
return "dual"
return f"other:{head}"
GROUPS = [
("enterprise", "Enterprise only: never open"),
("snippets", "Dual, holding Enterprise snippets: never open"),
("removed", "Removed"),
("dual", "Dual (AGPL-3.0-only OR LicenseRef-SEL): may be read"),
("none", "No license header: check before reading"),
]
prev = FORK_BASE
for tag in newer:
title = f"Review upstream web UI {tag}"
if title in existing:
print(f"{tag}: issue already exists."); prev = tag; continue
cmp = call("GET", f"https://api.github.com/repos/{UPSTREAM}/compare/{prev}...{tag}")
files = cmp.get("files", [])
groups = {}
for f in files:
if f["status"] == "removed":
group = "removed"
else:
group = license_of(tag, f["filename"])
line = f"- `{f['filename']}` ({f['status']}, +{f['additions']} −{f['deletions']})"
if f["status"] == "renamed":
line += f", from `{f['previous_filename']}`"
groups.setdefault(group, []).append(line)
date = cmp["commits"][-1]["commit"]["committer"]["date"][:10] if cmp.get("commits") else "?"
parts = [
f"Upstream tagged the web UI {tag} ({date}): {plural(cmp.get('total_commits', 0), 'commit')} and "
f"{plural(len(files), 'changed file')} since {prev}. The console was forked at {FORK_BASE} and "
"no longer merges upstream (README › Upstream).",
"Read what changed and note here anything worth writing ourselves, in our own code. "
"Open only files marked *may be read*. Never open a file marked *never open*, "
"and don't hand one to anyone who writes console code.",
]
if len(files) >= 300:
parts.append("**The compare stops at 300 files, so this list is incomplete.**")
labels = dict(GROUPS)
order = [g for g, _ in GROUPS] + sorted(g for g in groups if g not in labels)
for group in order:
if group not in groups:
continue
label = labels.get(group, f"Other license ({group[6:]}): check before reading")
parts.append(f"<details><summary>{label}: {len(groups[group])}</summary>\n\n"
+ "\n".join(sorted(groups[group])) + "\n\n</details>")
body = "\n\n".join(parts)
if DRY_RUN:
print(f"=== {title}\n{body}\n")
else:
issue = call("POST", f"{api}/issues", {"title": title, "body": body}, token=os.environ["TOKEN"])
print(f"{tag}: opened #{issue['number']}.")
prev = tag
PY
+3 -2
View File
@@ -52,8 +52,9 @@ npm run build
INBUXA Admin was forked from upstream at v1.0.11 and no longer takes upstream's
changes. Later upstream releases include code under a proprietary license, so
nothing after v1.0.11 is merged or copied in. Upstream releases are still
followed, and a feature worth having is written here, in this fork's own code.
nothing after v1.0.11 is merged or copied in. The console has diverged too far
to take anything from upstream, so its releases aren't followed either: new
features are this fork's own.
## Versions
+1 -1
View File
@@ -20,7 +20,7 @@
* built on, so the list is what actually differs rather than a guess. A file
* counts as upstream's when its header names Stalwart Labs as a copyright
* holder; files the fork wrote carry their own copyright and need nothing.
* When a newer upstream release is merged, move BASE to it.
* Upstream is no longer merged, so BASE stays at v1.0.11.
*/
import { execFileSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';