diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 0000000..7a85fac --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,34 @@ +{ + "features": { + "ghcr.io/devcontainer-community/devcontainer-features/yq:1": { + "version": "1.0.3", + "resolved": "ghcr.io/devcontainer-community/devcontainer-features/yq@sha256:9e73b838a7d821f89c8020b51c2605aa4174811d152c70b43f2e98a0f8413d35", + "integrity": "sha256:9e73b838a7d821f89c8020b51c2605aa4174811d152c70b43f2e98a0f8413d35" + }, + "ghcr.io/devcontainers-extra/features/apt-packages:1": { + "version": "1.0.6", + "resolved": "ghcr.io/devcontainers-extra/features/apt-packages@sha256:55c54412112da81b9381e470cdbbe55278564950d1ff536ce925b1e8e096babd", + "integrity": "sha256:55c54412112da81b9381e470cdbbe55278564950d1ff536ce925b1e8e096babd" + }, + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "1.1.0", + "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671", + "integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671" + }, + "ghcr.io/devcontainers/features/node:2": { + "version": "2.0.0", + "resolved": "ghcr.io/devcontainers/features/node@sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f", + "integrity": "sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f" + }, + "ghcr.io/devcontainers/features/rust:1": { + "version": "1.5.0", + "resolved": "ghcr.io/devcontainers/features/rust@sha256:0c55e65f2e3df736e478f26ee4d5ed41bae6b54dac1318c443e31444c8ed283c", + "integrity": "sha256:0c55e65f2e3df736e478f26ee4d5ed41bae6b54dac1318c443e31444c8ed283c" + }, + "ghcr.io/flexwie/devcontainer-features/op:1": { + "version": "1.0.0", + "resolved": "ghcr.io/flexwie/devcontainer-features/op@sha256:68098ff0ac2447bcf0eeee7ac19123bd0375fd43330ee3c9ca4dee510b74c6d3", + "integrity": "sha256:68098ff0ac2447bcf0eeee7ac19123bd0375fd43330ee3c9ca4dee510b74c6d3" + } + } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bc7c9bd..dd7fa40 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -44,3 +44,8 @@ updates: ignore: # Dependabot should not update Home Assistant as that should match the homeassistant key in hacs.json - dependency-name: "homeassistant" + # Each phac version pins exactly one HA version (1:1 mapping), so it must be + # updated manually together with HA_VERSION in scripts/setup/bootstrap + - dependency-name: "pytest-homeassistant-custom-component" + # pytest version is pinned by phac; updating it independently causes conflicts + - dependency-name: "pytest" diff --git a/requirements.txt b/requirements.txt index 27aca3e..46cfc05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,5 @@ pre-commit>=4.6.0,<4.7.0 ruff>=0.15.15,<0.16.0 zlib_ng>=1.0.0,<1.1.0 isal>=1.8.0,<1.9.0 -pytest>=9.0.3 -pytest-asyncio>=1.4.0 -pytest-homeassistant-custom-component>=0.13.334 +# Must match HA_VERSION in scripts/setup/bootstrap exactly (1:1 mapping) +pytest-homeassistant-custom-component==0.13.322 diff --git a/scripts/setup/bootstrap b/scripts/setup/bootstrap index 3042e8e..61f4679 100755 --- a/scripts/setup/bootstrap +++ b/scripts/setup/bootstrap @@ -46,6 +46,11 @@ fi # shellcheck source=/dev/null source "$HOME/.venv/bin/activate" +# Allow pre-release versions of homeassistant in this dev environment. +# phac pins an exact HA version (sometimes a beta/rc), and HA_VERSION may +# intentionally be set to a pre-release for testing purposes. +export UV_PRERELEASE=allow + log_header "Installing project dependencies" uv pip install --requirement requirements.txt diff --git a/scripts/setup/update-ha-version b/scripts/setup/update-ha-version new file mode 100755 index 0000000..e98dd73 --- /dev/null +++ b/scripts/setup/update-ha-version @@ -0,0 +1,106 @@ +#!/bin/bash + +# scripts/setup/update-ha-version: Update Home Assistant version across dev environment +# +# Finds the pytest-homeassistant-custom-component version that matches the given +# HA version (1:1 mapping on PyPI) and updates both scripts/setup/bootstrap and +# requirements.txt atomically. This is the single command to run when upgrading +# the development environment to a new Home Assistant version. +# +# Usage: +# ./scripts/setup/update-ha-version VERSION +# +# Examples: +# ./scripts/setup/update-ha-version 2026.5.4 +# ./scripts/setup/update-ha-version 2026.6.0b0 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR/../.." + +# shellcheck source=scripts/.lib/output.sh +source "$SCRIPT_DIR/../.lib/output.sh" + +BOOTSTRAP_FILE="scripts/setup/bootstrap" +REQUIREMENTS_FILE="requirements.txt" + +# --- Argument validation --- +if [[ $# -ne 1 ]]; then + log_error "Usage: $0 VERSION (e.g. 2026.5.4 or 2026.6.0b0)" + exit 1 +fi +NEW_HA_VERSION="$1" + +# --- Read current versions from files --- +CURRENT_HA_VERSION=$(grep -oP 'HA_VERSION:-"\K[^"]+' "$BOOTSTRAP_FILE") +CURRENT_PHAC_VERSION=$(grep -oP 'pytest-homeassistant-custom-component==\K\S+' "$REQUIREMENTS_FILE") + +log_header "Updating Home Assistant version" +log_info "Current: homeassistant==${CURRENT_HA_VERSION} / phac==${CURRENT_PHAC_VERSION}" +log_info "Target: homeassistant==${NEW_HA_VERSION}" +echo + +if [[ "$NEW_HA_VERSION" == "$CURRENT_HA_VERSION" ]]; then + log_warning "Already on homeassistant==${NEW_HA_VERSION}, nothing to do." + exit 0 +fi + +# --- Find matching phac version via PyPI --- +log_step "Querying PyPI for matching pytest-homeassistant-custom-component version..." + +NEW_PHAC_VERSION=$(python3 - "$NEW_HA_VERSION" <<'PYEOF' +import json, sys, urllib.request + +target_ha = sys.argv[1] + +# Fetch full list of all releases +url = "https://pypi.org/pypi/pytest-homeassistant-custom-component/json" +try: + data = json.loads(urllib.request.urlopen(url, timeout=15).read()) +except Exception as e: + print(f"ERROR: Could not reach PyPI: {e}", file=sys.stderr) + sys.exit(1) + +releases = sorted(data["releases"].keys(), reverse=True) + +for version in releases: + try: + pkg_url = f"https://pypi.org/pypi/pytest-homeassistant-custom-component/{version}/json" + pkg_data = json.loads(urllib.request.urlopen(pkg_url, timeout=10).read()) + requires = pkg_data["info"]["requires_dist"] or [] + ha_req = next((r for r in requires if r.startswith("homeassistant==")), None) + if ha_req and target_ha in ha_req: + print(version) + sys.exit(0) + except Exception: + pass + +print(f"ERROR: No phac version found for homeassistant=={target_ha}", file=sys.stderr) +sys.exit(1) +PYEOF +) || { + log_error "Could not find a pytest-homeassistant-custom-component version for homeassistant==${NEW_HA_VERSION}" + log_info "Check https://pypi.org/pypi/pytest-homeassistant-custom-component/json for available versions" + exit 1 +} + +log_result 0 "pytest-homeassistant-custom-component==${NEW_PHAC_VERSION}" +echo + +# --- Apply changes --- +log_step "Updating ${BOOTSTRAP_FILE}..." +sed -i "s/HA_VERSION:-\"${CURRENT_HA_VERSION}\"/HA_VERSION:-\"${NEW_HA_VERSION}\"/" "$BOOTSTRAP_FILE" + +log_step "Updating ${REQUIREMENTS_FILE}..." +sed -i "s/pytest-homeassistant-custom-component==${CURRENT_PHAC_VERSION}/pytest-homeassistant-custom-component==${NEW_PHAC_VERSION}/" "$REQUIREMENTS_FILE" + +# --- Summary --- +echo +log_success "Done! Updated to:" +log_info " homeassistant==${NEW_HA_VERSION} (in ${BOOTSTRAP_FILE})" +log_info " pytest-homeassistant-custom-component==${NEW_PHAC_VERSION} (in ${REQUIREMENTS_FILE})" +echo +log_info "Next steps:" +log_info " 1. Rebuild the DevContainer to apply the new environment" +log_info " 2. Run ./scripts/test to verify the test suite still passes"