diff --git a/packaging/README.md b/packaging/README.md index 2bfa86c..5e4c266 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -44,7 +44,11 @@ package carries no binaries and owes no `VERIFICATION.txt`. `chocolateybeforemod releases the WinDivert driver before an upgrade or an uninstall, because the kernel holds `WinDivert64.sys` open while it is loaded and an open file cannot be deleted. Its package folder is read-only for plain users, which is one of the two reasons the -program stopped keeping user files in its own directory. +program stopped keeping user files in its own directory. Its `iconUrl` is a CDN address +pinned to the release tag, and both halves are load-bearing: moderation refuses +`github.com///raw/...` exactly as it refuses `raw.githubusercontent.com`, +and an icon pointing at a branch would keep changing under a package that is already +approved. **WinGet.** `ArchiveBinariesDependOnPath: true` is the line that matters. The default for a portable inside an archive is a symlink, and this executable cannot be reached diff --git a/packaging/chocolatey/bean-network-tester.nuspec.in b/packaging/chocolatey/bean-network-tester.nuspec.in index 692d16d..1d45532 100644 --- a/packaging/chocolatey/bean-network-tester.nuspec.in +++ b/packaging/chocolatey/bean-network-tester.nuspec.in @@ -9,7 +9,7 @@ {{APP_NAME}} {{PUBLISHER}} {{PROJECT_URL}} - {{REPO_URL}}/raw/master/bean.png + {{ICON_URL}} {{COPYRIGHT}} {{LICENSE_URL}} false diff --git a/tests/test_mutation_registry.py b/tests/test_mutation_registry.py index a4d239b..bc1ba97 100644 --- a/tests/test_mutation_registry.py +++ b/tests/test_mutation_registry.py @@ -2152,6 +2152,15 @@ "new": " pass", "test": "test_the_converted_readout_appears_only_when_there_is_something_to_convert", }, + { + # The exact line Chocolatey's moderation refused, and the branch pin that + # came with it. One line carries both faults, so one mutation restores both. + "label": "packaging: the Chocolatey icon goes back to a branch on github.com", + "file": "packaging/chocolatey/bean-network-tester.nuspec.in", + "old": " {{ICON_URL}}", + "new": " {{REPO_URL}}/raw/master/bean.png", + "test": "test_the_chocolatey_icon_is_a_pinned_cdn_url", + }, ] # The runner's own check: a patch that cannot compile must be reported as BROKEN, not diff --git a/tests/test_packaging.py b/tests/test_packaging.py index 1a50738..dc864a1 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -117,6 +117,25 @@ def test_the_download_is_checksummed(tmp_path, monkeypatch): "-Checksum64 '94359ea6" in install and "-ChecksumType64 'sha256'" in install) +def test_the_chocolatey_icon_is_a_pinned_cdn_url(tmp_path, monkeypatch): + """Not `github.com/.../raw/`, and not a branch. Both were wrong until 2026-09-05. + + Chocolatey's moderation held 0.5.0 back over the first half: it treats + `github.com///raw/...` exactly as `raw.githubusercontent.com`, and + neither is a CDN. The second half is the one nobody had complained about, and it + is the one that bites later - an icon pinned to a BRANCH follows whatever that + branch does next, under a package that is already approved and out of reach. + """ + nuspec = _rendered(tmp_path, monkeypatch)["chocolatey/bean-network-tester.nuspec"] + found = re.search(r"(.*?)", nuspec) + check("the nuspec carries an icon at all", found is not None) + url = found.group(1) if found else "" + for host in ("raw.githubusercontent.com", "github.com"): + check(f"the icon is not served from {host}", host not in url, f"({url})") + check("the icon is pinned to the tag being packaged, not to a branch", + f"@v{appinfo.__version__}/" in url, f"({url})") + + # -- the inputs that would look fine and be wrong ------------------------------- # def test_the_binary_marker_never_reaches_the_url(tmp_path, monkeypatch): """`sha256sum` writes ` *`, and that star is not part of the name.""" diff --git a/tools/build_packages.py b/tools/build_packages.py index 2066175..0e3119f 100644 --- a/tools/build_packages.py +++ b/tools/build_packages.py @@ -101,6 +101,7 @@ def values(version, digest, asset): home = _read_json("site", "pages", "home", "page.json")["languages"]["en"] tagline = _read_json("site", "i18n", "en.json")["site.tagline"] repo = site["repo_url"].rstrip("/") + slug = repo.split("github.com/", 1)[-1] # owner/name, which is what a CDN wants tag = f"v{version}" # Every entry here is used by a template, and a test keeps it that way in both # directions: an unknown placeholder is a typo, a dead entry is a manifest that @@ -123,6 +124,14 @@ def values(version, digest, asset): "PROJECT_URL": site["base_url"].rstrip("/"), "REPO_URL": repo, "RELEASE_NOTES_URL": f"{repo}/releases/tag/{tag}", + # 🔴 A CDN, and PINNED to the tag - Chocolatey's moderation held 0.5.0 back over + # this on 2026-09-05. `{repo}/raw/...` is treated as raw.githubusercontent.com, + # which is not a CDN; jsDelivr, Statically and the Githack variants are. The + # BRANCH was the second fault, and nobody had complained about it yet: an icon on + # `master` follows whatever the branch does next, under a package that is already + # approved. Same bytes either way (measured that day: 21 155 B, image/png, from + # both hosts), so this moves the address and not the picture. + "ICON_URL": f"https://cdn.jsdelivr.net/gh/{slug}@{tag}/bean.png", "RELEASE_DATE": release_date(version), "TAGLINE": tagline, "DESCRIPTION": home["description"],