Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 126 additions & 9 deletions actions/winget-submit/test_winget_submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ def test_render_bootstrap_manifests_updates_values_without_mutating_source(tmp_p
"PackageIdentifier": package_id,
"PackageVersion": "0.0.0",
"DefaultLocale": "en-US",
"ManifestType": "version",
"ManifestVersion": "1.10.0",
"ManifestType": " version ",
"ManifestVersion": " 1.9.0 ",
},
)
_write_manifest(
Expand All @@ -161,8 +161,8 @@ def test_render_bootstrap_manifests_updates_values_without_mutating_source(tmp_p
"Publisher": "Open CLI Collective",
"PackageName": "Code Review CLI",
"ShortDescription": "Automated pull-request review CLI",
"ManifestType": "defaultLocale",
"ManifestVersion": "1.10.0",
"ManifestType": " defaultLocale ",
"ManifestVersion": " 1.9.0 ",
},
)
installer_path = winget_dir / f"{package_id}.installer.yaml"
Expand All @@ -184,8 +184,8 @@ def test_render_bootstrap_manifests_updates_values_without_mutating_source(tmp_p
"InstallerSha256": "CHECKSUM_ARM64_PLACEHOLDER",
},
],
"ManifestType": "installer",
"ManifestVersion": "1.10.0",
"ManifestType": " installer ",
"ManifestVersion": " 1.9.0 ",
},
)
original_installer = installer_path.read_text()
Expand Down Expand Up @@ -218,19 +218,86 @@ def test_render_bootstrap_manifests_updates_values_without_mutating_source(tmp_p
f"{package_id}.installer.yaml",
}
assert all(pathlib.Path(path).is_relative_to(rendered) for path in output_paths)
rendered_version = yaml.safe_load((rendered / f"{package_id}.yaml").read_text())
rendered_locale = yaml.safe_load((rendered / f"{package_id}.locale.en-US.yaml").read_text())
rendered_version_text = (rendered / f"{package_id}.yaml").read_text()
rendered_locale_text = (rendered / f"{package_id}.locale.en-US.yaml").read_text()
rendered_installer_text = (rendered / f"{package_id}.installer.yaml").read_text()
assert (
rendered_version_text.splitlines()[0]
== "# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.9.0.schema.json"
)
assert (
rendered_locale_text.splitlines()[0]
== "# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.9.0.schema.json"
)
assert (
rendered_installer_text.splitlines()[0]
== "# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.9.0.schema.json"
)
rendered_version = yaml.safe_load(rendered_version_text)
rendered_locale = yaml.safe_load(rendered_locale_text)
assert rendered_version["PackageVersion"] == "1.2.3"
assert rendered_version["ManifestType"] == "version"
assert rendered_version["ManifestVersion"] == "1.9.0"
assert rendered_locale["PackageVersion"] == "1.2.3"
rendered_installer = yaml.safe_load((rendered / f"{package_id}.installer.yaml").read_text())
assert rendered_locale["ManifestType"] == "defaultLocale"
assert rendered_locale["ManifestVersion"] == "1.9.0"
rendered_installer = yaml.safe_load(rendered_installer_text)
assert rendered_installer["PackageVersion"] == "1.2.3"
assert rendered_installer["ManifestType"] == "installer"
assert rendered_installer["ManifestVersion"] == "1.9.0"
installers = {item["Architecture"]: item for item in rendered_installer["Installers"]}
assert installers["x64"]["InstallerUrl"] == assets.x64.url
assert installers["x64"]["InstallerSha256"] == "a" * 64
assert installers["arm64"]["InstallerUrl"] == assets.arm64.url
assert installers["arm64"]["InstallerSha256"] == "b" * 64


@pytest.mark.parametrize("manifest_name", ["version", "locale", "installer"])
@pytest.mark.parametrize("missing_key", ["ManifestType", "ManifestVersion"])
Comment thread
monit-reviewer marked this conversation as resolved.
def test_render_bootstrap_manifests_requires_schema_metadata(tmp_path, manifest_name, missing_key):
package_id = "OpenCLICollective.codereview-cli"
source = tmp_path / "tool"
winget_dir = source / "packaging" / "winget"
winget_dir.mkdir(parents=True)
manifests = _bootstrap_manifest_templates(winget_dir, package_id)
manifests[manifest_name][1].pop(missing_key)
for path, data in manifests.values():
_write_manifest(path, data)

rendered = tmp_path / "rendered"
with pytest.raises(winget_submit.SubmitError, match=missing_key):
winget_submit.render_bootstrap_manifests(
package_id=package_id,
version="1.2.3",
working_dir=source,
output_dir=rendered,
assets=_assets(),
)
assert not rendered.exists()


def test_render_bootstrap_manifests_rejects_unknown_manifest_type(tmp_path):
package_id = "OpenCLICollective.codereview-cli"
source = tmp_path / "tool"
winget_dir = source / "packaging" / "winget"
winget_dir.mkdir(parents=True)
manifests = _bootstrap_manifest_templates(winget_dir, package_id)
manifests["version"][1]["ManifestType"] = "versions"
for path, data in manifests.values():
_write_manifest(path, data)

rendered = tmp_path / "rendered"
with pytest.raises(winget_submit.SubmitError, match="ManifestType"):
winget_submit.render_bootstrap_manifests(
package_id=package_id,
version="1.2.3",
working_dir=source,
output_dir=rendered,
assets=_assets(),
)
assert not rendered.exists()


def test_render_bootstrap_manifests_rejects_output_inside_source(tmp_path):
source = tmp_path / "tool"
source.mkdir()
Expand Down Expand Up @@ -409,6 +476,56 @@ def _write_manifest(path, data):
path.write_text(yaml.safe_dump(data, sort_keys=False))


def _bootstrap_manifest_templates(winget_dir, package_id):
return {
"version": (
winget_dir / f"{package_id}.yaml",
{
"PackageIdentifier": package_id,
"PackageVersion": "0.0.0",
"DefaultLocale": "en-US",
"ManifestType": "version",
"ManifestVersion": "1.10.0",
},
),
"locale": (
winget_dir / f"{package_id}.locale.en-US.yaml",
{
"PackageIdentifier": package_id,
"PackageVersion": "0.0.0",
"PackageLocale": "en-US",
"Publisher": "Open CLI Collective",
"PackageName": "Code Review CLI",
"ShortDescription": "Automated pull-request review CLI",
"ManifestType": "defaultLocale",
"ManifestVersion": "1.10.0",
},
),
"installer": (
winget_dir / f"{package_id}.installer.yaml",
{
"PackageIdentifier": package_id,
"PackageVersion": "0.0.0",
"InstallerType": "zip",
"Installers": [
{
"Architecture": "x64",
"InstallerUrl": "old-x64",
"InstallerSha256": "old-x64-sha",
},
{
"Architecture": "arm64",
"InstallerUrl": "old-arm64",
"InstallerSha256": "old-arm64-sha",
},
],
"ManifestType": "installer",
"ManifestVersion": "1.10.0",
},
),
}


def _assets():
return winget_submit.WindowsAssets(
x64=winget_submit.WindowsAsset("x64.zip", "https://example.test/x64.zip", "a"),
Expand Down
37 changes: 32 additions & 5 deletions actions/winget-submit/winget_submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
HTTP_TIMEOUT_SECONDS = 30
WINGETCREATE_DOWNLOAD_TIMEOUT_SECONDS = 60
WINGETCREATE_COMMAND_TIMEOUT_SECONDS = 300
WINGET_MANIFEST_TYPES = {"version", "installer", "defaultLocale", "locale"}


class SubmitError(Exception):
Expand Down Expand Up @@ -174,17 +175,18 @@ def render_bootstrap_manifests(
if missing:
raise SubmitError(f"winget bootstrap template missing: {', '.join(missing)}")

output.mkdir(parents=True, exist_ok=True)
rendered: list[Path] = []
rendered: list[tuple[Path, str]] = []
for src in [version_manifest, *locale_manifests, installer_manifest]:
data = _load_manifest(src, package_id)
data["PackageVersion"] = version
if src == installer_manifest:
_update_installer_manifest(data, assets)
dest = output / src.name
dest.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
rendered.append(dest)
return rendered
rendered.append((dest, _dump_manifest(data, src)))
output.mkdir(parents=True, exist_ok=True)
for dest, text in rendered:
dest.write_text(text, encoding="utf-8")
return [dest for dest, _ in rendered]


def build_update_command(wingetcreate: Path, package_id: str, version: str, assets: WindowsAssets, token: str) -> list[str]:
Expand Down Expand Up @@ -330,6 +332,31 @@ def _update_installer_manifest(data: dict, assets: WindowsAssets) -> None:
raise SubmitError(f"installer manifest missing architectures: {', '.join(missing)}")


def _dump_manifest(data: dict, path: Path) -> str:
manifest_type = _required_manifest_string(data, "ManifestType", path)
manifest_version = _required_manifest_string(data, "ManifestVersion", path)
if manifest_type not in WINGET_MANIFEST_TYPES:
raise SubmitError(f"{path}: ManifestType must be one of {sorted(WINGET_MANIFEST_TYPES)}")
rendered = dict(data)
rendered["ManifestType"] = manifest_type
rendered["ManifestVersion"] = manifest_version
schema = (
"https://aka.ms/"
f"winget-manifest.{manifest_type}.{manifest_version}.schema.json"
)
return (
f"# yaml-language-server: $schema={schema}\n\n"
f"{yaml.safe_dump(rendered, sort_keys=False)}"
)
Comment thread
monit-reviewer marked this conversation as resolved.


def _required_manifest_string(data: dict, key: str, path: Path) -> str:
value = data.get(key)
if not isinstance(value, str) or not value.strip():
raise SubmitError(f"{path}: {key} is required for bootstrap rendering")
return value.strip()


def _github_request(url: str, token: str, accept: str) -> Request:
headers = {
"Accept": accept,
Expand Down
Loading