Skip to content
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.34.5] - 2026/05/20

### Added

- `CrossBuildEnvReleaseSpec` now has a `published_at` field containing the UTC timestamp
when the release was published on GitHub (ISO 8601 format). This is exposed in
`pyodide xbuildenv search --all` and `pyodide xbuildenv search --all --json`.
[#349](https://github.com/pyodide/pyodide-build/pull/349)

## [0.34.4] - 2026/05/15

Expand Down
1 change: 1 addition & 0 deletions pyodide_build/cli/xbuildenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ def _search(
"min": release.min_pyodide_build_version,
"max": release.max_pyodide_build_version,
},
published_at=release.published_at,
compatible=release.is_compatible(
python_version=local["python"],
pyodide_build_version=local["pyodide-build"],
Expand Down
4 changes: 3 additions & 1 deletion pyodide_build/tests/test_cli_xbuildenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,12 @@ def test_xbuildenv_search(
"Python",
"Emscripten",
"pyodide-build",
"Published",
"Compatible",
]

row1 = lines[3].strip().split("│")[1:-1]
assert [col.strip() for col in row1] == ["0.1.0", "4.5.6", "1.39.8", "-", "No"]
assert [col.strip() for col in row1] == ["0.1.0", "4.5.6", "1.39.8", "-", "", "No"]


def test_xbuildenv_search_json(tmp_path, fake_xbuildenv_releases_compatible):
Expand Down Expand Up @@ -390,6 +391,7 @@ def test_xbuildenv_search_json(tmp_path, fake_xbuildenv_releases_compatible):
"python",
"emscripten",
"pyodide_build",
"published_at",
"compatible",
}, f"Environment {environment} has unexpected keys: {environment.keys()}"

Expand Down
25 changes: 25 additions & 0 deletions pyodide_build/tests/test_xbuildenv_releases.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,31 @@ def test_is_compatible_full():
)


def test_published_at():
release = CrossBuildEnvReleaseSpec(
version="0.1.0",
url="https://example.com/0.1.0.tar.gz",
sha256="1234567890abcdef",
python_version="3.8.0",
emscripten_version="1.39.8",
published_at="2024-01-18T21:52:25Z",
)
assert release.published_at == "2024-01-18T21:52:25Z"

release_no_date = CrossBuildEnvReleaseSpec(
version="0.1.0",
url="https://example.com/0.1.0.tar.gz",
sha256="1234567890abcdef",
python_version="3.8.0",
emscripten_version="1.39.8",
)
assert release_no_date.published_at is None

metadata = CrossBuildEnvMetaSpec(**FAKE_METADATA)
for release in metadata.releases.values():
assert release.published_at is None


def test_is_compatible_without_pyodide_build_range():
release = CrossBuildEnvReleaseSpec(
version="0.1.0",
Expand Down
7 changes: 6 additions & 1 deletion pyodide_build/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class MetadataView:
emscripten: str
pyodide_build: dict[str, str | None]
compatible: bool
published_at: str | None = None
Comment thread
agriyakhetarpal marked this conversation as resolved.
Outdated

@classmethod
def to_table(cls, views: list["MetadataView"]) -> str:
Expand All @@ -21,6 +22,7 @@ def to_table(cls, views: list["MetadataView"]) -> str:
("Python", 10),
("Emscripten", 10),
("pyodide-build", 25),
("Published", 10),
("Compatible", 10),
]

Expand Down Expand Up @@ -59,12 +61,14 @@ def to_table(cls, views: list["MetadataView"]) -> str:
pyodide_build_range = (
f"{view.pyodide_build['min'] or ''} - {view.pyodide_build['max'] or ''}"
)
published = (view.published_at or "")[:10]
row = [
f"{view.version:<{columns[0][1]}}",
f"{view.python:<{columns[1][1]}}",
f"{view.emscripten:<{columns[2][1]}}",
f"{pyodide_build_range:<{columns[3][1]}}",
f"{'Yes' if view.compatible else 'No':<{columns[4][1]}}",
f"{published:<{columns[4][1]}}",
f"{'Yes' if view.compatible else 'No':<{columns[5][1]}}",
]
table.append(
vertical + vertical.join(f" {cell} " for cell in row) + vertical
Expand All @@ -82,6 +86,7 @@ def to_json(cls, views: list["MetadataView"]) -> str:
"python": view.python,
"emscripten": view.emscripten,
"pyodide_build": view.pyodide_build,
"published_at": view.published_at,
"compatible": view.compatible,
}
for view in views
Expand Down
4 changes: 3 additions & 1 deletion pyodide_build/xbuildenv_releases.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ class CrossBuildEnvReleaseSpec(BaseModel):
python_version: str
# The version of the Emscripten SDK
emscripten_version: str
# The UTC timestamp when the release was published on GitHub (ISO 8601)
published_at: str | None = None
# Minimum and maximum pyodide-build versions that are compatible with this release
min_pyodide_build_version: str | None = None
max_pyodide_build_version: str | None = None
model_config = ConfigDict(extra="forbid", title="CrossBuildEnvReleasesSpec")
model_config = ConfigDict(extra="ignore", title="CrossBuildEnvReleasesSpec")

@property
def python_version_tuple(self) -> tuple[int, int, int]:
Expand Down