fix(bootstrap-installer): stamp setup app version from release semver
Fixes #107177 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@hermes/bootstrap-installer",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"version": "0.21.1",
|
||||
"description": "Hermes Setup — signed installer that drives scripts/install.ps1 with a polished native UI.",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hermes-bootstrap"
|
||||
version = "0.0.1"
|
||||
version = "0.21.1"
|
||||
description = "Hermes Setup — signed installer that drives scripts/install.ps1"
|
||||
authors = ["Nous Research <info@nousresearch.com>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Hermes",
|
||||
"version": "0.0.1",
|
||||
"version": "0.21.1",
|
||||
"identifier": "com.nousresearch.hermes.setup",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
@@ -2230,6 +2230,60 @@ def update_version_files(semver: str, calver_date: str):
|
||||
)
|
||||
desktop_pkg.write_text(pkg_text, encoding="utf-8")
|
||||
|
||||
# Keep the bootstrap installer (Hermes-Setup.dmg CFBundleShortVersionString)
|
||||
# in lockstep with the Python package version. Tauri reads `version` from
|
||||
# package.json + tauri.conf.json; a hardcoded 0.0.1 ships in the DMG.
|
||||
installer_pkg = REPO_ROOT / "apps" / "bootstrap-installer" / "package.json"
|
||||
if installer_pkg.exists():
|
||||
pkg_text = installer_pkg.read_text(encoding="utf-8")
|
||||
pkg_text = re.sub(
|
||||
r'("version"\s*:\s*)"[^"]+"',
|
||||
rf'\g<1>"{semver}"',
|
||||
pkg_text,
|
||||
count=1,
|
||||
)
|
||||
installer_pkg.write_text(pkg_text, encoding="utf-8")
|
||||
|
||||
installer_tauri = (
|
||||
REPO_ROOT / "apps" / "bootstrap-installer" / "src-tauri" / "tauri.conf.json"
|
||||
)
|
||||
if installer_tauri.exists():
|
||||
pkg_text = installer_tauri.read_text(encoding="utf-8")
|
||||
pkg_text = re.sub(
|
||||
r'("version"\s*:\s*)"[^"]+"',
|
||||
rf'\g<1>"{semver}"',
|
||||
pkg_text,
|
||||
count=1,
|
||||
)
|
||||
installer_tauri.write_text(pkg_text, encoding="utf-8")
|
||||
|
||||
installer_cargo = (
|
||||
REPO_ROOT / "apps" / "bootstrap-installer" / "src-tauri" / "Cargo.toml"
|
||||
)
|
||||
if installer_cargo.exists():
|
||||
cargo_text = installer_cargo.read_text(encoding="utf-8")
|
||||
cargo_text = re.sub(
|
||||
r'^version\s*=\s*"[^"]+"',
|
||||
f'version = "{semver}"',
|
||||
cargo_text,
|
||||
count=1,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
installer_cargo.write_text(cargo_text, encoding="utf-8")
|
||||
|
||||
|
||||
def version_files_to_stage() -> list[str]:
|
||||
"""Return version-bearing files that exist and should be `git add`ed after a bump."""
|
||||
candidates = [
|
||||
VERSION_FILE,
|
||||
PYPROJECT_FILE,
|
||||
REPO_ROOT / "apps" / "desktop" / "package.json",
|
||||
REPO_ROOT / "apps" / "bootstrap-installer" / "package.json",
|
||||
REPO_ROOT / "apps" / "bootstrap-installer" / "src-tauri" / "tauri.conf.json",
|
||||
REPO_ROOT / "apps" / "bootstrap-installer" / "src-tauri" / "Cargo.toml",
|
||||
]
|
||||
return [str(path) for path in candidates if path.exists()]
|
||||
|
||||
|
||||
def resolve_author(name: str, email: str) -> str:
|
||||
"""Resolve a git author to a GitHub @mention."""
|
||||
@@ -2568,7 +2622,7 @@ def main():
|
||||
print(f" ✓ Updated version files to v{new_version} ({calver_date})")
|
||||
|
||||
# Commit version bump
|
||||
add_files = [str(VERSION_FILE), str(PYPROJECT_FILE)]
|
||||
add_files = version_files_to_stage()
|
||||
add_result = git_result("add", *add_files)
|
||||
if add_result.returncode != 0:
|
||||
print(f" ✗ Failed to stage version files: {add_result.stderr.strip()}")
|
||||
|
||||
144
tests/scripts/test_release_bootstrap_installer_version.py
Normal file
144
tests/scripts/test_release_bootstrap_installer_version.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""release.py must stamp bootstrap-installer versions with the release semver.
|
||||
|
||||
Tauri CFBundleShortVersionString is read from
|
||||
apps/bootstrap-installer/src-tauri/tauri.conf.json (and the sibling
|
||||
package.json). Those files were hardcoded 0.0.1 and omitted from
|
||||
update_version_files / the --publish --bump git add list, so Hermes-Setup.dmg
|
||||
always shipped 0.0.1. Same class as the desktop stamp (#68783 / PR #68796).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parents[2] / "scripts" / "release.py"
|
||||
|
||||
|
||||
def _load():
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"release_bootstrap_installer_version", SCRIPT
|
||||
)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
release = _load()
|
||||
|
||||
|
||||
def _json_version(path: Path) -> str:
|
||||
return json.loads(path.read_text(encoding="utf-8"))["version"]
|
||||
|
||||
|
||||
def _patch_repo(tmp_path, monkeypatch, *, with_installer: bool = True):
|
||||
repo = tmp_path
|
||||
init_py = repo / "hermes_cli" / "__init__.py"
|
||||
init_py.parent.mkdir(parents=True)
|
||||
init_py.write_text(
|
||||
'__version__ = "0.0.1"\n__release_date__ = "2026.1.1"\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
pyproject = repo / "pyproject.toml"
|
||||
pyproject.write_text('version = "0.0.1"\n', encoding="utf-8")
|
||||
|
||||
desktop_pkg = repo / "apps" / "desktop" / "package.json"
|
||||
desktop_pkg.parent.mkdir(parents=True)
|
||||
desktop_pkg.write_text('{"version":"0.0.1"}\n', encoding="utf-8")
|
||||
|
||||
installer_pkg = repo / "apps" / "bootstrap-installer" / "package.json"
|
||||
tauri_conf = (
|
||||
repo / "apps" / "bootstrap-installer" / "src-tauri" / "tauri.conf.json"
|
||||
)
|
||||
cargo_toml = repo / "apps" / "bootstrap-installer" / "src-tauri" / "Cargo.toml"
|
||||
if with_installer:
|
||||
tauri_conf.parent.mkdir(parents=True)
|
||||
installer_pkg.write_text(
|
||||
'{"name":"x","version":"0.0.1"}\n', encoding="utf-8"
|
||||
)
|
||||
tauri_conf.write_text(
|
||||
'{"productName":"Hermes","version":"0.0.1"}\n', encoding="utf-8"
|
||||
)
|
||||
cargo_toml.write_text('[package]\nversion = "0.0.1"\n', encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(release, "REPO_ROOT", repo)
|
||||
monkeypatch.setattr(release, "VERSION_FILE", init_py)
|
||||
monkeypatch.setattr(release, "PYPROJECT_FILE", pyproject)
|
||||
return {
|
||||
"repo": repo,
|
||||
"init_py": init_py,
|
||||
"pyproject": pyproject,
|
||||
"desktop_pkg": desktop_pkg,
|
||||
"installer_pkg": installer_pkg,
|
||||
"tauri_conf": tauri_conf,
|
||||
"cargo_toml": cargo_toml,
|
||||
}
|
||||
|
||||
|
||||
def test_update_version_files_stamps_bootstrap_installer(tmp_path, monkeypatch):
|
||||
paths = _patch_repo(tmp_path, monkeypatch)
|
||||
|
||||
release.update_version_files("0.21.1", "2026.9.10")
|
||||
|
||||
assert _json_version(paths["installer_pkg"]) == "0.21.1"
|
||||
assert _json_version(paths["tauri_conf"]) == "0.21.1"
|
||||
assert 'version = "0.21.1"' in paths["cargo_toml"].read_text(encoding="utf-8")
|
||||
|
||||
# CONTROL: existing desktop / Python stamps still happen.
|
||||
assert _json_version(paths["desktop_pkg"]) == "0.21.1"
|
||||
assert 'version = "0.21.1"' in paths["pyproject"].read_text(encoding="utf-8")
|
||||
init_text = paths["init_py"].read_text(encoding="utf-8")
|
||||
assert '__version__ = "0.21.1"' in init_text
|
||||
assert '__release_date__ = "2026.9.10"' in init_text
|
||||
|
||||
|
||||
def test_update_version_files_skips_missing_installer_dir(tmp_path, monkeypatch):
|
||||
paths = _patch_repo(tmp_path, monkeypatch, with_installer=False)
|
||||
|
||||
release.update_version_files("0.21.1", "2026.9.10")
|
||||
|
||||
assert not paths["installer_pkg"].exists()
|
||||
assert not paths["tauri_conf"].exists()
|
||||
assert _json_version(paths["desktop_pkg"]) == "0.21.1"
|
||||
assert 'version = "0.21.1"' in paths["pyproject"].read_text(encoding="utf-8")
|
||||
assert '__version__ = "0.21.1"' in paths["init_py"].read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_update_version_files_does_not_invent_version_keys(tmp_path, monkeypatch):
|
||||
paths = _patch_repo(tmp_path, monkeypatch)
|
||||
original = '{"name":"x","productName":"Hermes"}\n'
|
||||
paths["installer_pkg"].write_text(original, encoding="utf-8")
|
||||
paths["tauri_conf"].write_text(original, encoding="utf-8")
|
||||
|
||||
release.update_version_files("0.21.1", "2026.9.10")
|
||||
|
||||
assert paths["installer_pkg"].read_text(encoding="utf-8") == original
|
||||
assert paths["tauri_conf"].read_text(encoding="utf-8") == original
|
||||
assert _json_version(paths["desktop_pkg"]) == "0.21.1"
|
||||
|
||||
|
||||
def test_version_files_to_stage_includes_installer_when_present(tmp_path, monkeypatch):
|
||||
paths = _patch_repo(tmp_path, monkeypatch)
|
||||
|
||||
staged = release.version_files_to_stage()
|
||||
|
||||
assert str(paths["init_py"]) in staged
|
||||
assert str(paths["pyproject"]) in staged
|
||||
assert str(paths["desktop_pkg"]) in staged
|
||||
assert str(paths["installer_pkg"]) in staged
|
||||
assert str(paths["tauri_conf"]) in staged
|
||||
assert str(paths["cargo_toml"]) in staged
|
||||
|
||||
|
||||
def test_version_files_to_stage_omits_missing_installer(tmp_path, monkeypatch):
|
||||
paths = _patch_repo(tmp_path, monkeypatch, with_installer=False)
|
||||
|
||||
staged = release.version_files_to_stage()
|
||||
|
||||
assert str(paths["installer_pkg"]) not in staged
|
||||
assert str(paths["tauri_conf"]) not in staged
|
||||
assert str(paths["cargo_toml"]) not in staged
|
||||
assert str(paths["desktop_pkg"]) in staged
|
||||
assert str(paths["init_py"]) in staged
|
||||
assert str(paths["pyproject"]) in staged
|
||||
Reference in New Issue
Block a user