diff --git a/.cordon-guard.sha256 b/.cordon-guard.sha256 index a49162055..7f53052fc 100644 --- a/.cordon-guard.sha256 +++ b/.cordon-guard.sha256 @@ -10,7 +10,7 @@ 8f18558b9b0f22dbafd357a754153b9bfa1a062c225d31e6e968faf7174b4800 .github/workflows/refresh-advisories.yml 780f65837105fbf623728041037e989b8e1fcc72e8833fae7165fcc1b1fc7259 .github/workflows/refresh-iac-policies.yml 5558889702d2cb8eeccdc749ca71523049258ceec6370c900cfe88e6891ab05f .github/workflows/refresh-intel.yml -dea3355632950d45a1e8be75bf5927925e684daac0fc051ab31ea501e8b4271c .github/workflows/release.yml +eccf8d9dd77ed2023968ea91ad34faab2cd6f0a0a409ce882b23ca8250e8a89b .github/workflows/release.yml e5274385e6ab7ef1e65b0de1eba419e21c5d745d9436d10c994ec7d184f9db10 .github/workflows/tests-linux.yml 3fb78c694f2444babd21eaa2201b645285dce3e5d89dc30f17e88b45faf61cc8 .github/workflows/verify-refresh.yml 0c724d7f65652eed6bcab9a3e3c5cb9559b5c63cb998728c81c65d71e05d2ac5 cordon-history-baseline.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69a3f7f8f..eb4e3407e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -549,10 +549,14 @@ jobs: needs: [build, publish, confirm] # Only for a real tag push. A `workflow_dispatch` verification run must not # create a release object for a version that already exists. + # Also on the second run a tag push makes once PyPI has the version (publish skipped, confirm + # passed): a release page this job failed to create the first time is created then, and an + # existing one only has its notes refreshed. if: >- - startsWith(github.ref, 'refs/tags/v') + !cancelled() && startsWith(github.ref, 'refs/tags/v') && needs.build.result == 'success' - && needs.publish.result == 'success' + && (needs.publish.result == 'success' + || (needs.publish.result == 'skipped' && needs.confirm.result == 'success')) runs-on: ubuntu-latest permissions: contents: write @@ -572,8 +576,14 @@ jobs: # Built from the tagged source, tested, and attached beside the wheel; the editor # extension and the scanner it runs are released together and carry one version. working-directory: editors/vscode + # The marketplace icon is a PNG rendered from images/icon.svg, never committed: rendered + # here as editors/vscode/Dockerfile renders it. Without it vsce refuses to package, which + # stopped the 0.7.0 release here after PyPI had published (6 Nov). run: | set -euo pipefail + sudo apt-get update -qq + sudo apt-get install -qq -y --no-install-recommends librsvg2-bin + rsvg-convert -w 256 -h 256 images/icon.svg -o images/icon.png npm ci --no-audit --no-fund npm test npx --yes @vscode/vsce@3.6.0 package --no-dependencies -o "../../artefacts/cordon-${GITHUB_REF_NAME#v}.vsix" diff --git a/editors/vscode/Dockerfile b/editors/vscode/Dockerfile index 5da0e6bd8..1d9bb6222 100644 --- a/editors/vscode/Dockerfile +++ b/editors/vscode/Dockerfile @@ -8,7 +8,7 @@ COPY . . RUN rsvg-convert -w 256 -h 256 images/icon.svg -o images/icon.png \ && npm ci --ignore-scripts \ && npm test \ - && npx --yes @vscode/vsce@4.0.0 package --no-dependencies -o /build/cordon.vsix + && npx --yes @vscode/vsce@3.6.0 package --no-dependencies -o /build/cordon.vsix FROM scratch COPY --from=build /build/cordon.vsix / diff --git a/tests/unit/repository/release/test_release_artefacts.py b/tests/unit/repository/release/test_release_artefacts.py index ff6f8e4c2..d2dafbbcf 100644 --- a/tests/unit/repository/release/test_release_artefacts.py +++ b/tests/unit/repository/release/test_release_artefacts.py @@ -278,6 +278,49 @@ def test_the_action_pin_is_still_generated(self, step: str) -> None: assert step in self.body() +class TestTheReleasePackagesTheExtension: + """The 0.7.0 release published to PyPI, then stopped creating its GitHub release: vsce refused + to package an extension whose icon, images/icon.png, is rendered at build time and was not + rendered in the release step (6 Nov).""" + + EXTENSION = ROOT / "editors" / "vscode" + + @staticmethod + def packaging_step() -> str: + text = RELEASE.read_text(encoding="utf-8") + start = text.index("- name: Build the VS Code extension") + return text[start : text.index("- name:", start + 1)] + + def test_every_file_the_manifest_names_is_committed_or_rendered_first(self) -> None: + manifest = json.loads((self.EXTENSION / "package.json").read_text(encoding="utf-8")) + step = self.packaging_step() + named = [manifest["icon"]] if "icon" in manifest else [] + for path in named: + if (self.EXTENSION / path).is_file(): + continue + assert f"-o {path}" in step, ( + f"{path} is neither committed nor rendered before packaging" + ) + assert step.index(f"-o {path}") < step.index("vsce@"), f"{path} is rendered after vsce" + + def test_the_release_and_the_dockerfile_package_with_one_vsce(self) -> None: + import re + + dockerfile = (self.EXTENSION / "Dockerfile").read_text(encoding="utf-8") + versions = set(re.findall(r"@vscode/vsce@([0-9.]+)", RELEASE.read_text(encoding="utf-8"))) + assert versions == set(re.findall(r"@vscode/vsce@([0-9.]+)", dockerfile)), versions + + def test_the_release_page_is_created_on_the_run_after_publishing_too(self) -> None: + text = RELEASE.read_text(encoding="utf-8") + job = text[ + text.index(" github-release:") : text.index( + " steps:", text.index(" github-release:") + ) + ] + assert "!cancelled()" in job, "a skipped publish would skip the job without it" + assert "needs.publish.result == 'skipped' && needs.confirm.result == 'success'" in job + + class TestReleaseWorkflowPublishesTheImage: """The Dockerfile existed and nothing built or pushed it -- README called this out explicitly ("no published container image yet"). These assert