From addd37467d935098f2248a77b3c51446e0ae6200 Mon Sep 17 00:00:00 2001 From: SAPHID Date: Wed, 28 Oct 2026 12:26:24 +0000 Subject: [PATCH] ci: fail installer previews when expected outputs are missing --- .github/workflows/release.yml | 14 +++++++++++++- tests/test_release_workflow.py | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c6fe3fd..7f01bf3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -113,6 +113,18 @@ jobs: run: npm ci && npm run ${{ matrix.script }} env: CSC_IDENTITY_AUTO_DISCOVERY: "false" + - name: Check installer outputs before upload + shell: bash + run: | + # Every platform must produce each advertised file type. An artifact + # upload can otherwise succeed with just one file from the path list. + read -r -a patterns <<< '${{ matrix.files }}' + for pattern in "${patterns[@]}"; do + if ! compgen -G "$pattern" >/dev/null; then + echo "missing installer output matching $pattern" >&2 + exit 1 + fi + done - name: Check the Mac bundle matches its runner if: runner.os == 'macOS' shell: bash @@ -189,7 +201,7 @@ jobs: app/dist/*.zip app/dist/*.AppImage app/dist/*.deb - if-no-files-found: ignore + if-no-files-found: error checksums: needs: build diff --git a/tests/test_release_workflow.py b/tests/test_release_workflow.py index 7800bf6..0e7f666 100644 --- a/tests/test_release_workflow.py +++ b/tests/test_release_workflow.py @@ -22,6 +22,7 @@ WORKFLOW = ROOT / ".github" / "workflows" / "release.yml" FAKEGH = ROOT / "tests" / "fakegh" STEP = "- name: Create the draft release" UPLOAD_STEP = "- name: Upload to the release" +OUTPUTS_STEP = "- name: Check installer outputs before upload" def step_script(step=STEP): @@ -116,6 +117,28 @@ class DraftStep(unittest.TestCase): self.assertEqual(self.patches(log), []) +@unittest.skipUnless(os.name != "nt" and shutil.which("bash"), + "needs a POSIX bash; Actions uses Git Bash on Windows") +class InstallerOutputsStep(unittest.TestCase): + def run_step(self, files): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + for name in files: + (root / name).write_bytes(b"installer") + script = step_script(OUTPUTS_STEP).replace("${{ matrix.files }}", "*.dmg *.zip") + return subprocess.run(["bash", "--noprofile", "--norc", "-eo", "pipefail", "-c", script], + cwd=root, capture_output=True, text=True, timeout=10) + + def test_requires_each_advertised_installer_type(self): + result = self.run_step(["Frame-Control-mac.dmg"]) + self.assertNotEqual(result.returncode, 0) + self.assertIn("missing installer output matching *.zip", result.stderr) + + def test_accepts_all_advertised_installer_types(self): + result = self.run_step(["Frame-Control-mac.dmg", "Frame-Control-mac.zip"]) + self.assertEqual(result.returncode, 0, result.stderr) + + @unittest.skipUnless(os.name != "nt" and shutil.which("bash") and shutil.which("jq"), "needs a POSIX bash and jq; Actions runs this step under bash") class UploadStep(unittest.TestCase):