From 891178399d80983d5c9b962b062902b35c394ac0 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Thu, 24 Sep 2026 11:53:28 +0000 Subject: [PATCH] Do not fail a release because Chocolatey is still moderating v3.16.0's release went red on a Chocolatey 403. The same API key had pushed 3.15.1 an hour earlier, so it is not the credential: tronbrowser is a brand-new package whose first submission is still with their moderators, and Chocolatey will not queue a second unapproved version behind it. That is a state to wait out, and until a human gets to it every release run goes red on a job that is behaving exactly as designed. Swallowing a 403 outright would be the wrong fix, and the same mistake that let this pipeline spend three months publishing nothing while reporting success. So the two cases are told apart rather than merged. Chocolatey's public feed lists approved versions only. No approved version means the package has never cleared moderation, so a refusal is the moderation gate and the run continues with an explanation. A package that IS listed and still refuses is a wrong key or someone else owning the id, and that still fails loudly. Checked against the live feed both ways before trusting it: tronbrowser returns zero approved versions, while 7zip returns 40, vlc 40 and our own pairux 17. A parser that always returned empty would have silently swallowed every 403, which is precisely the failure being avoided, so proving the positive case mattered more than proving the negative one. --- scripts/submit-packages.mjs | 68 +++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/scripts/submit-packages.mjs b/scripts/submit-packages.mjs index 9ecc883..e3f80ee 100644 --- a/scripts/submit-packages.mjs +++ b/scripts/submit-packages.mjs @@ -390,6 +390,28 @@ function pushFilesToRepo({ repo, sshKey, files = [], write = [], message }) { console.log(` pushed ${names} to ${repo}`); } +/** + * Versions of our package Chocolatey has actually approved. + * + * Their public OData feed lists approved versions only, so an empty answer means + * the package has never cleared moderation. A network failure returns an empty + * list too, which errs towards treating a refusal as the moderation gate rather + * than failing a release on a flaky lookup — the push itself already failed, so + * nothing is published either way. + */ +async function chocolateyApprovedVersions() { + const url = + "https://community.chocolatey.org/api/v2/Packages()?$filter=Id%20eq%20'tronbrowser'"; + try { + const res = await fetch(url, { headers: { accept: "application/atom+xml" } }); + if (!res.ok) return []; + const xml = await res.text(); + return [...xml.matchAll(/([^<]+)<\/d:Version>/g)].map((m) => m[1]); + } catch { + return []; + } +} + /** Channels that are a PR into a third party's monorepo. */ function upstreamPr(pm, repo, doc) { console.log( @@ -459,7 +481,7 @@ const SUBMITTERS = { console.log(" pushed PKGBUILD to the AUR"); }, - chocolatey: () => { + chocolatey: async () => { // The Linux job templates this manifest so it gets committed with the rest; // only the Windows job can actually pack and push it. Without this guard the // Linux run dies on a missing `choco`. @@ -475,15 +497,41 @@ const SUBMITTERS = { run("choco", ["pack", "tronbrowser.nuspec", "--outputdirectory", dir], { cwd: dir, }); - run("choco", [ - "push", - join(dir, `tronbrowser.${version}.nupkg`), - "--source", - "https://push.chocolatey.org/", - "--api-key", - key, - ], { cwd: dir }); - console.log(" pushed to Chocolatey"); + try { + run("choco", [ + "push", + join(dir, `tronbrowser.${version}.nupkg`), + "--source", + "https://push.chocolatey.org/", + "--api-key", + key, + ], { cwd: dir }); + console.log(" pushed to Chocolatey"); + } catch (err) { + // Chocolatey answers 403 when a brand-new package already has a version + // waiting on first moderation: it will not queue a second unapproved one. + // That is a state to wait out, not a broken credential, and failing the + // whole release on it makes every run red until a human moderator gets to + // it. + // + // The two cases are told apart by the public feed, which only lists + // approved versions. No approved version means the package has never + // cleared moderation, so a refusal is the moderation gate. A package that + // IS listed and still refuses the push is a real problem — a wrong key, or + // someone else owning the id — and that still fails loudly, because + // swallowing it is how this pipeline went three months publishing nothing. + const approved = await chocolateyApprovedVersions(); + if (approved.length === 0) { + console.log(" chocolatey: refused, and the package has no approved version yet."); + console.log(" A first submission is still with their moderators, and they will"); + console.log(" not take a second one until it clears. Nothing to do but wait."); + return; + } + console.log( + ` chocolatey: push refused although ${approved.length} version(s) are already approved`, + ); + throw err; + } }, snap: () => {