Skip to content

Commit 1d61c00

Browse files
committed
Switch to HACS zip releases; drop the Python packaging stack
HACS installs custom_components/run_chicken directly and never uses a Python wheel, so the hatch build + uv-dynamic-versioning machinery never reached users — every release shipped manifest version 0.0.0. - hacs.json: set zip_release + filename so HACS pulls the release zip, whose manifest carries the tag version. - release.yml: drop `uv build`; stamp the tag version into the manifest and zip custom_components/run_chicken with files at the archive root (where HACS extracts them). - inject_manifest_version.py: reduce to a small CLI injector; remove the hatch build hook and copy-dir mode. - pyproject.toml: remove [build-system], hatch, and uv-dynamic-versioning config; set [tool.uv] package = false so uv manages the dev env without building the project (also avoids transitive sdist build failures). - scripts: delete redundant `lint`; point `develop` at `uv run hass`.
1 parent 2b2eba6 commit 1d61c00

7 files changed

Lines changed: 47 additions & 258 deletions

File tree

.github/workflows/release.yml

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,18 @@ jobs:
1414
steps:
1515
- name: Checkout
1616
uses: actions/checkout@v6
17-
with:
18-
fetch-depth: 0
19-
20-
- name: Set up Python
21-
uses: actions/setup-python@v6
22-
with:
23-
python-version: '3.13'
24-
25-
- name: Install uv
26-
run: pipx install uv
27-
28-
- name: Build sdist & wheel (version injected via Hatch hook)
29-
run: |
30-
uv build
3117

3218
- name: Compute version from tag
3319
id: ver
34-
run: |
35-
TAG="${GITHUB_REF_NAME}"
36-
if [[ "$TAG" == v* ]]; then VERSION="${TAG:1}"; else VERSION="$TAG"; fi
37-
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
20+
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
3821

39-
- name: Create HACS zip with injected manifest
22+
- name: Stamp manifest version and build HACS zip
4023
run: |
41-
python scripts/inject_manifest_version.py --copy-dir . staging_pkg --version "${{ steps.ver.outputs.version }}"
42-
(cd staging_pkg && zip -r "ha-run-chicken-${{ steps.ver.outputs.version }}.zip" custom_components)
24+
python3 scripts/inject_manifest_version.py --version "${{ steps.ver.outputs.version }}"
4325
mkdir -p dist
44-
mv staging_pkg/ha-run-chicken-*.zip dist/
26+
# HACS extracts the zip directly into custom_components/run_chicken,
27+
# so the integration files must sit at the root of the archive.
28+
(cd custom_components/run_chicken && zip -r "${GITHUB_WORKSPACE}/dist/ha-run-chicken.zip" .)
4529
4630
- name: List artifacts
4731
run: ls -lah dist
@@ -50,7 +34,4 @@ jobs:
5034
uses: softprops/action-gh-release@v2
5135
with:
5236
generate_release_notes: true
53-
files: |
54-
dist/*.whl
55-
dist/*.tar.gz
56-
dist/*.zip
37+
files: dist/ha-run-chicken.zip

hacs.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"name": "Run-Chicken",
33
"homeassistant": "2025.2.4",
4-
"hacs": "2.0.5"
4+
"hacs": "2.0.5",
5+
"zip_release": true,
6+
"filename": "ha-run-chicken.zip"
57
}

pyproject.toml

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "ha-run-chicken"
3-
description = "Add your description here"
4-
dynamic = ["version"]
3+
version = "0.0.0"
4+
description = "Home Assistant integration for the Run-Chicken automatic chicken door"
55
readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
@@ -17,30 +17,10 @@ dev = [
1717
"pip>=21.3.1",
1818
"pre-commit>=4.5.0",
1919
"ruff>=0.15",
20-
"uv-dynamic-versioning>=0.6.0",
2120
]
2221

23-
[build-system]
24-
requires = [
25-
"hatchling>=1.25.0",
26-
"uv-dynamic-versioning>=0.6.0",
27-
]
28-
build-backend = "hatchling.build"
29-
30-
[tool.hatch.version]
31-
source = "uv-dynamic-versioning"
32-
33-
[tool.hatch.build]
34-
include = [
35-
"custom_components",
36-
"scripts/inject_manifest_version.py",
37-
]
38-
39-
[tool.uv-dynamic-versioning]
40-
fallback-version = "0.0.0"
41-
42-
[tool.hatch.build.hooks.custom]
43-
path = "scripts/inject_manifest_version.py"
44-
22+
# This is a Home Assistant / HACS integration, not a Python package: HACS ships
23+
# custom_components/run_chicken directly and never installs a wheel. Tell uv not
24+
# to build or install the project itself; it only manages the dev environment.
4525
[tool.uv]
46-
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true }}]
26+
package = false

scripts/develop

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ cd "$(dirname "$0")/.."
77
# Create config dir if not present
88
if [[ ! -d "${PWD}/config" ]]; then
99
mkdir -p "${PWD}/config"
10-
hass --config "${PWD}/config" --script ensure_config
10+
uv run hass --config "${PWD}/config" --script ensure_config
1111
fi
1212

1313
# Set the path to custom_components
@@ -17,4 +17,4 @@ fi
1717
export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components"
1818

1919
# Start Home Assistant
20-
hass --config "${PWD}/config" --debug
20+
uv run hass --config "${PWD}/config" --debug

scripts/inject_manifest_version.py

Lines changed: 27 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,59 @@
11
"""
2-
Inject git tag into manifest.json.
2+
Inject a version string into the Home Assistant manifest.json.
33
4-
A utility module for handling version injection into a manifest.json file and managing build hooks
5-
for Home Assistant custom integrations.
6-
7-
This module provides a Hatch build hook (CustomHook) for version injection into
8-
`manifest.json`, resolving versions from Git tags when operating outside of Hatch, and
9-
a CLI for creating zip releases with the computed version values.
10-
11-
The exported functionality includes:
12-
- Resolving versions based on Git tags.
13-
- Injecting version data into `manifest.json`.
14-
- A CLI entry point for performing version injection.
15-
16-
Classes:
17-
- CustomHook: A custom build hook for Hatch to inject computed version into `manifest.json`.
18-
19-
Functions:
20-
- resolve_version_from_git_tag: Resolves the integration version, falling back to `0.0.0`
21-
when no Git tag is available.
22-
- _cli: Handles the command-line interface for direct invocation of the versioning process.
23-
24-
Constants:
25-
- MANIFEST_REL_PATH: Relative path to the `manifest.json` file within a Home Assistant custom component.
4+
Used by the release workflow to stamp the git tag version into
5+
``custom_components/run_chicken/manifest.json`` before packaging the HACS zip.
6+
The repository copy stays at the ``0.0.0`` placeholder; the version is only
7+
injected into the ephemeral CI checkout that gets zipped.
268
"""
279

2810
import argparse
2911
import json
3012
import os
31-
import shutil
3213
import subprocess
3314
import sys
3415
from pathlib import Path
3516

36-
try:
37-
# Available during Hatch builds
38-
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
39-
except ImportError:
40-
# Not available outside Hatch so we have CustomHook use object as its base class
41-
BuildHookInterface = object # type: ignore[misc,assignment]
42-
43-
4417
MANIFEST_REL_PATH = Path("custom_components/run_chicken/manifest.json")
4518

4619

47-
def _read_json(path: Path) -> dict:
48-
with path.open("r", encoding="utf-8") as f:
49-
return json.load(f)
50-
51-
52-
def _write_json(path: Path, data: dict) -> None:
53-
path.parent.mkdir(parents=True, exist_ok=True)
54-
with path.open("w", encoding="utf-8") as f:
55-
json.dump(data, f, indent=2, ensure_ascii=False)
56-
f.write("\n")
57-
58-
59-
def _inject_version(manifest_path: Path, version: str) -> None:
60-
data = _read_json(manifest_path)
61-
data["version"] = version
62-
_write_json(manifest_path, data)
63-
64-
6520
def resolve_version_from_git_tag() -> str:
6621
"""
67-
Best-effort version resolution from Git tags when outside Hatch.
22+
Resolve the version from the git tag, dropping a leading ``v``.
6823
69-
Returns the tag without a leading 'v' if present. Falls back to '0.0.0'.
24+
Prefers the ref name provided by CI (``GITHUB_REF_NAME``) and falls back to
25+
``git describe``. Returns ``0.0.0`` when no tag is available.
7026
"""
7127
try:
72-
# Prefer the ref name provided by CI
7328
ref_name = os.environ.get("GITHUB_REF_NAME")
74-
if ref_name:
75-
tag = ref_name
76-
else:
77-
tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"], text=True).strip()
78-
29+
tag = ref_name or subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"], text=True).strip()
7930
except (OSError, subprocess.CalledProcessError):
8031
return "0.0.0"
81-
8232
return tag.removeprefix("v")
8333

8434

85-
class CustomHook(BuildHookInterface): # type: ignore[misc]
86-
"""
87-
Hatch custom build hook to inject the computed version into manifest.json.
88-
89-
This operates on a staged temp copy and force-includes it for both wheel and sdist,
90-
leaving the repository file untouched.
91-
"""
92-
93-
def initialize(self, version: str, build_data: dict) -> None: # type: ignore[override]
94-
"""
95-
Occurs immediately before each build.
96-
97-
Any modifications to the build data will be seen by the build target.
98-
"""
99-
# "version" passed to this function only tells you the _kind_ of build: "standard"
100-
version = self.metadata.version
101-
102-
_inject_version(MANIFEST_REL_PATH, version)
103-
104-
# Force-include the modified manifest at the same relative path
105-
force_include = build_data.setdefault("force_include", {})
106-
force_include[str(MANIFEST_REL_PATH)] = str(MANIFEST_REL_PATH)
107-
108-
109-
def _cli(argv: list[str]) -> int:
110-
"""CLI entrypoint used by GitHub Actions to create a zip release."""
111-
parser = argparse.ArgumentParser(description="Inject version into Home Assistant manifest.json")
112-
parser.add_argument("--version", help="Version to inject; if omitted, derived from git tag.")
113-
group = parser.add_mutually_exclusive_group(required=True)
114-
group.add_argument("--file", type=Path, help="Path to a manifest.json to rewrite in-place.")
115-
group.add_argument(
116-
"--copy-dir",
117-
nargs=2,
118-
metavar=("SRC_DIR", "DEST_DIR"),
119-
help=(
120-
"Copy the integration directory from SRC_DIR to DEST_DIR and rewrite the copied "
121-
"manifest.json with the resolved version."
122-
),
35+
def inject_version(manifest_path: Path, version: str) -> None:
36+
"""Write ``version`` into the manifest at ``manifest_path``."""
37+
data = json.loads(manifest_path.read_text(encoding="utf-8"))
38+
data["version"] = version
39+
manifest_path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
40+
41+
42+
def main(argv: list[str]) -> int:
43+
"""Inject a version into a manifest.json (CLI entry point)."""
44+
parser = argparse.ArgumentParser(description="Inject a version into manifest.json")
45+
parser.add_argument("--version", help="Version to inject; derived from the git tag if omitted.")
46+
parser.add_argument(
47+
"--file",
48+
type=Path,
49+
default=MANIFEST_REL_PATH,
50+
help="Path to the manifest.json to rewrite in place.",
12351
)
124-
12552
args = parser.parse_args(argv)
126-
version = args.version or resolve_version_from_git_tag()
127-
128-
if args.file:
129-
_inject_version(args.file, version)
130-
return 0
131-
132-
src_dir, dest_dir = map(Path, args.copy_dir)
133-
if not (src_dir / MANIFEST_REL_PATH).is_file():
134-
parser.error(f"Could not find {MANIFEST_REL_PATH} under {src_dir}")
13553

136-
# Copy tree, then rewrite manifest in the copied location
137-
if dest_dir.exists():
138-
shutil.rmtree(dest_dir)
139-
shutil.copytree(src_dir, dest_dir)
140-
copied_manifest = dest_dir / MANIFEST_REL_PATH
141-
_inject_version(copied_manifest, version)
54+
inject_version(args.file, args.version or resolve_version_from_git_tag())
14255
return 0
14356

14457

14558
if __name__ == "__main__":
146-
raise SystemExit(_cli(sys.argv[1:]))
59+
raise SystemExit(main(sys.argv[1:]))

scripts/lint

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)