From 35c716013f6f8f3c0143d3297b1eab86eeed3391 Mon Sep 17 00:00:00 2001 From: champ Date: Mon, 20 Jul 2026 12:53:30 +0800 Subject: [PATCH 1/2] fix: repair v0.7.1 release packaging --- .github/workflows/release.yml | 13 ++++--- CHANGELOG.md | 4 ++- tests/test_release_contract.py | 66 ++++++++++++++++++++++++++++++++++ tools/server/build_linux.sh | 4 +-- 4 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 tests/test_release_contract.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49b186c..ac9590c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,14 +36,19 @@ jobs: python -m pip install --upgrade pip python -m pip install -r tools/requirements.txt python -m pip install -r tools/server/requirements.txt + python -m pip install pyinstaller - name: Build with PyInstaller - run: pyinstaller --noconfirm --clean tools/server/citeseal_server.spec + run: python -m PyInstaller --noconfirm --clean --distpath tools/dist --workpath tools/build tools/server/citeseal_server.spec - name: Verify exe run: | - tools\dist\citeseal_server.exe & $env:CITESEAL_PORT = "8765" - Start-Sleep -Seconds 5 - (Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:8765/api/health").Content + $server = Start-Process -FilePath "tools/dist/citeseal_server.exe" -PassThru + try { + Start-Sleep -Seconds 5 + (Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:8765/api/health").Content + } finally { + Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue + } - name: Upload artifact uses: actions/upload-artifact@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9ac35..cdaf291 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [v0.7.1] - 2026-07-20 -Maintenance release restoring cross-platform demo execution and documenting the repository maintenance process. +Maintenance release restoring cross-platform demo execution and release artifact builds, while documenting the repository maintenance process. ### Fixed - Made the live demo's terminal output ASCII-safe and its JSON file I/O explicitly UTF-8, preventing `UnicodeEncodeError` failures on legacy Windows console encodings. - Added a `cp1252` subprocess regression test so Windows console compatibility remains covered by the full test suite. +- Repaired the Windows release job by installing and invoking PyInstaller explicitly, fixing its output path, and setting the health-check port before server startup. +- Corrected the Linux tarball source layout so bundled scripts remain directly importable; added release packaging contract tests for both platforms. ### Added - ADR (Architecture Decision Records): `docs/adr/0001-agent-bundle-as-agent-consumption-layer.md`, `docs/adr/0002-local-first-boundary.md` diff --git a/tests/test_release_contract.py b/tests/test_release_contract.py new file mode 100644 index 0000000..06ac68d --- /dev/null +++ b/tests/test_release_contract.py @@ -0,0 +1,66 @@ +"""Release packaging contracts for the distributable server artifacts.""" +from __future__ import annotations + +import os +import re +import subprocess +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_windows_release_installs_pyinstaller_before_build() -> None: + workflow = (ROOT / ".github" / "workflows" / "release.yml").read_text( + encoding="utf-8" + ) + windows_job = workflow.split(" windows-exe:", 1)[1].split( + " linux-tarball:", 1 + )[0] + before_build, after_build = windows_job.split( + " - name: Build with PyInstaller", 1 + ) + + assert re.search( + r"python -m pip install(?:[^\n]*\s)?pyinstaller(?:[<>=!~].*)?$", + before_build, + flags=re.IGNORECASE | re.MULTILINE, + ), "Windows release job must install PyInstaller before invoking it" + assert "python -m PyInstaller" in after_build + assert "--distpath tools/dist" in after_build + + +def test_windows_release_sets_health_port_before_starting_server() -> None: + workflow = (ROOT / ".github" / "workflows" / "release.yml").read_text( + encoding="utf-8" + ) + windows_job = workflow.split(" windows-exe:", 1)[1].split( + " linux-tarball:", 1 + )[0] + verify_step = windows_job.split(" - name: Verify exe", 1)[1].split( + " - name: Upload artifact", 1 + )[0] + + port_index = verify_step.find('$env:CITESEAL_PORT = "8765"') + start_index = verify_step.find("Start-Process") + health_index = verify_step.find("http://127.0.0.1:8765/api/health") + assert min(port_index, start_index, health_index) >= 0 + assert port_index < start_index < health_index + + +def test_linux_bundle_copies_scripts_into_flat_import_path(tmp_path: Path) -> None: + build_script = ROOT / "tools" / "server" / "build_linux.sh" + copy_line = next( + line.strip() + for line in build_script.read_text(encoding="utf-8").splitlines() + if line.strip().startswith('cp -r "$TOOLS/scripts') + ) + stage_root = tmp_path / "citeseal_server-linux-x64" + (stage_root / "scripts").mkdir(parents=True) + env = os.environ.copy() + env.update({"TOOLS": str(ROOT / "tools"), "ROOT": str(stage_root)}) + + subprocess.run(["bash", "-c", copy_line], env=env, check=True) + + assert (stage_root / "scripts" / "ci_common.py").is_file() + assert not (stage_root / "scripts" / "scripts").exists() diff --git a/tools/server/build_linux.sh b/tools/server/build_linux.sh index 4134521..e12bcf1 100644 --- a/tools/server/build_linux.sh +++ b/tools/server/build_linux.sh @@ -24,8 +24,8 @@ mkdir -p "$ROOT/bin" "$ROOT/venv" "$ROOT/scripts" echo "[1/5] Copying source..." cp -r "$TOOLS/server" "$ROOT/server" -cp -r "$TOOLS/scripts" "$ROOT/scripts" -cp "$TOOLS/citeseal.py" "$ROOT/" +cp -r "$TOOLS/scripts/." "$ROOT/scripts/" +cp "$TOOLS/citeseal.py" "$ROOT/" cp "$TOOLS/requirements.txt" "$ROOT/" cp "$TOOLS/server/requirements.txt" "$ROOT/server-requirements.txt" From 3aaa0aa6e4aa48a6b2cd8a975eb1a6611b571338 Mon Sep 17 00:00:00 2001 From: champ Date: Mon, 20 Jul 2026 12:56:17 +0800 Subject: [PATCH 2/2] test: scope Linux packaging check to POSIX --- tests/test_release_contract.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_release_contract.py b/tests/test_release_contract.py index 06ac68d..a8df9b9 100644 --- a/tests/test_release_contract.py +++ b/tests/test_release_contract.py @@ -6,6 +6,8 @@ import subprocess from pathlib import Path +import pytest + ROOT = Path(__file__).resolve().parents[1] @@ -48,6 +50,7 @@ def test_windows_release_sets_health_port_before_starting_server() -> None: assert port_index < start_index < health_index +@pytest.mark.skipif(os.name == "nt", reason="Linux bundle contract runs on POSIX") def test_linux_bundle_copies_scripts_into_flat_import_path(tmp_path: Path) -> None: build_script = ROOT / "tools" / "server" / "build_linux.sh" copy_line = next(