Skip to content

Commit 38a76ca

Browse files
committed
Add package release smoke
1 parent 8b2aff8 commit 38a76ca

7 files changed

Lines changed: 181 additions & 3 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,6 @@ reach the Python 3.9 matrix job:
493493
```bash
494494
python scripts/check-python39-compat.py
495495
```
496+
497+
498+
Release packages can be validated with `scripts/package-release-smoke.sh --output-dir dist/package-release-smoke`.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!--
2+
Copyright (C) 2026 Marco Fortina
3+
SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
# Package release smoke
7+
8+
The package release smoke verifies that the published artifacts can be built and installed from the wheel.
9+
10+
```bash
11+
scripts/package-release-smoke.sh --output-dir dist/package-release-smoke
12+
```
13+
14+
The smoke builds the source distribution and wheel, runs `twine check` when `twine` is installed, installs the wheel in a temporary virtual environment and executes deterministic CLI checks from the installed package.
15+
16+
The output directory contains:
17+
18+
```text
19+
summary.txt
20+
manifest.json
21+
SHA256SUMS
22+
logs/
23+
*.tar.gz
24+
*.whl
25+
```
26+
27+
Use `--skip-twine` only when running in a minimal environment without the release validation dependencies.

docs/reference/release.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ Run the full local gate before tagging:
3131
scripts/release-check.sh
3232
```
3333

34+
For package-only release validation:
35+
36+
```bash
37+
scripts/package-release-smoke.sh --output-dir dist/package-release-smoke
38+
```
39+
3440
The script verifies:
3541

3642
- the `CHANGELOG.md` section for the package version;
@@ -44,7 +50,7 @@ The script verifies:
4450
- unit and documentation regression tests;
4551
- `automax plugins coverage --strict`;
4652
- strict MkDocs build;
47-
- source distribution and wheel package smoke.
53+
- source distribution and wheel package release smoke.
4854

4955
For a metadata-only pass while debugging documentation or CI wiring:
5056

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ nav:
7474
- Policy and Risk Preflight: guides/policy-risk-preflight.md
7575
- Approval Gates: guides/approval-gates.md
7676
- Runtime Evidence Index: guides/runtime-evidence-index.md
77+
- Package Release Smoke: guides/package-release-smoke.md
7778
- Run Event Stream: guides/run-event-stream.md
7879
- Publishing Documentation: guides/publishing-docs.md
7980
- Reference:

scripts/package-release-smoke.sh

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/env bash
2+
# Copyright (C) 2026 Marco Fortina
3+
# SPDX-License-Identifier: AGPL-3.0-or-later
4+
5+
set -euo pipefail
6+
7+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8+
OUTPUT_DIR="${AUTOMAX_PACKAGE_RELEASE_SMOKE_OUTPUT_DIR:-${ROOT}/dist/package-release-smoke}"
9+
SKIP_TWINE="${AUTOMAX_PACKAGE_RELEASE_SMOKE_SKIP_TWINE:-0}"
10+
KEEP_WORK="${AUTOMAX_PACKAGE_RELEASE_SMOKE_KEEP_WORK:-0}"
11+
12+
usage() {
13+
cat <<'EOF'
14+
Usage: scripts/package-release-smoke.sh [--output-dir DIR] [--skip-twine]
15+
16+
Build source/wheel distributions, verify metadata when twine is available,
17+
install the wheel into a temporary virtualenv and run deterministic CLI smoke
18+
commands from the installed package.
19+
EOF
20+
}
21+
22+
while [[ $# -gt 0 ]]; do
23+
case "$1" in
24+
--output-dir)
25+
OUTPUT_DIR="$2"
26+
shift 2
27+
;;
28+
--skip-twine)
29+
SKIP_TWINE=1
30+
shift
31+
;;
32+
--help|-h)
33+
usage
34+
exit 0
35+
;;
36+
*)
37+
echo "unknown argument: $1" >&2
38+
usage >&2
39+
exit 2
40+
;;
41+
esac
42+
done
43+
44+
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/automax-package-release-smoke.XXXXXX")"
45+
cleanup() {
46+
if [[ "${KEEP_WORK}" != "1" ]]; then
47+
rm -rf "${WORK_DIR}"
48+
else
49+
echo "Kept work directory: ${WORK_DIR}"
50+
fi
51+
}
52+
trap cleanup EXIT
53+
54+
DIST_DIR="${WORK_DIR}/dist"
55+
VENV_DIR="${WORK_DIR}/venv"
56+
LOG_DIR="${OUTPUT_DIR}/logs"
57+
mkdir -p "${DIST_DIR}" "${LOG_DIR}"
58+
rm -f "${OUTPUT_DIR}/summary.txt" "${OUTPUT_DIR}/manifest.json" "${OUTPUT_DIR}/SHA256SUMS"
59+
60+
cd "${ROOT}"
61+
62+
run_step() {
63+
local name="$1"
64+
shift
65+
echo "== ${name} =="
66+
"$@" >"${LOG_DIR}/${name}.log" 2>&1
67+
}
68+
69+
run_step build python -m build --sdist --wheel --outdir "${DIST_DIR}"
70+
71+
if [[ "${SKIP_TWINE}" != "1" ]]; then
72+
if python -m twine --version >/dev/null 2>&1; then
73+
run_step twine-check python -m twine check "${DIST_DIR}"/*
74+
else
75+
echo "twine not available; set up dev dependencies to enable metadata check" >"${LOG_DIR}/twine-check.log"
76+
fi
77+
else
78+
echo "skipped by --skip-twine" >"${LOG_DIR}/twine-check.log"
79+
fi
80+
81+
run_step venv python -m venv "${VENV_DIR}"
82+
# shellcheck source=/dev/null
83+
. "${VENV_DIR}/bin/activate"
84+
python -m pip install --upgrade pip >"${LOG_DIR}/pip-upgrade.log" 2>&1
85+
run_step install-wheel python -m pip install "${DIST_DIR}"/*.whl
86+
run_step cli-version automax --version
87+
run_step cli-doctor automax doctor
88+
run_step cli-plugins automax plugins list
89+
run_step cli-schema automax schema export --kind job
90+
91+
deactivate
92+
93+
cp "${DIST_DIR}"/* "${OUTPUT_DIR}/"
94+
(
95+
cd "${OUTPUT_DIR}"
96+
sha256sum ./*.{tar.gz,whl} > SHA256SUMS
97+
)
98+
99+
python - <<PY
100+
from __future__ import annotations
101+
102+
import json
103+
from pathlib import Path
104+
105+
out = Path(${OUTPUT_DIR@Q})
106+
artifacts = []
107+
for path in sorted(list(out.glob("*.tar.gz")) + list(out.glob("*.whl"))):
108+
artifacts.append({"name": path.name, "size": path.stat().st_size})
109+
manifest = {
110+
"format": "automax-package-release-smoke-v1",
111+
"ok": True,
112+
"artifacts": artifacts,
113+
"logs": sorted(path.name for path in (out / "logs").glob("*.log")),
114+
}
115+
(out / "manifest.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
116+
lines = ["Package release smoke: OK", f"artifacts={len(artifacts)}"]
117+
lines.extend(f"- {item['name']} ({item['size']} bytes)" for item in artifacts)
118+
(out / "summary.txt").write_text("\n".join(lines) + "\n", encoding="utf-8")
119+
PY
120+
121+
cat "${OUTPUT_DIR}/summary.txt"

scripts/release-check.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ if [[ "${AUTOMAX_RELEASE_CHECK_SKIP_DOCS:-0}" != "1" ]]; then
110110
fi
111111

112112
if [[ "${AUTOMAX_RELEASE_CHECK_SKIP_PACKAGE:-0}" != "1" ]]; then
113-
scripts/package-smoke.sh
113+
scripts/package-release-smoke.sh --output-dir "${TMP_DIR}/package-release-smoke"
114114
fi
115115

116116
echo "Release check passed"

tests/test_documentation_and_regressions.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2097,6 +2097,26 @@ def test_runtime_evidence_suite_is_documented_and_optional():
20972097
assert "set AUTOMAX_SSH_HOST and AUTOMAX_SSH_USER" in result.stderr
20982098

20992099

2100+
def test_package_release_smoke_script_is_documented_and_release_wired():
2101+
script = Path("scripts/package-release-smoke.sh").read_text(encoding="utf-8")
2102+
release = Path("scripts/release-check.sh").read_text(encoding="utf-8")
2103+
docs = "\n".join(
2104+
path.read_text(encoding="utf-8")
2105+
for path in [
2106+
Path("docs/guides/package-release-smoke.md"),
2107+
Path("docs/reference/release.md"),
2108+
Path("mkdocs.yml"),
2109+
]
2110+
)
2111+
2112+
assert "automax-package-release-smoke-v1" in script
2113+
assert "python -m build --sdist --wheel" in script
2114+
assert "python -m twine check" in script
2115+
assert "automax schema export --kind job" in script
2116+
assert "scripts/package-release-smoke.sh" in release
2117+
assert "Package Release Smoke: guides/package-release-smoke.md" in docs
2118+
2119+
21002120
def test_database_smoke_script_runs_sqlite_and_lists_engines(tmp_path: Path):
21012121
script = Path("scripts/database-smoke.sh")
21022122

@@ -2179,7 +2199,7 @@ def test_release_check_script_covers_public_release_guardrails():
21792199
assert "docs generate-plugins" in text
21802200
assert "examples/runbooks/RUNBOOK_INDEX.md is stale" in text
21812201
assert "plugins coverage --strict" in text
2182-
assert "scripts/package-smoke.sh" in text
2202+
assert "scripts/package-release-smoke.sh" in text
21832203
assert "AUTOMAX_RELEASE_CHECK_SKIP_DOCS" in text
21842204
assert "AUTOMAX_RELEASE_CHECK_SKIP_PACKAGE" in text
21852205
assert "Check release metadata guardrails" in ci

0 commit comments

Comments
 (0)