From 136606a435594d0242a9381e1b45955f45ba057c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 6 Feb 2026 14:20:55 +0100 Subject: [PATCH] wip --- esphome/__main__.py | 6 +++--- esphome/bundle.py | 27 ++++++++++++++++----------- esphome/yaml_util.py | 6 +++--- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 9e06e63d2d..a572913582 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -966,7 +966,7 @@ def command_clean(args: ArgsProtocol, config: ConfigType) -> int | None: def command_bundle(args: ArgsProtocol, config: ConfigType) -> int | None: - from esphome.bundle import ConfigBundleCreator + from esphome.bundle import BUNDLE_EXTENSION, ConfigBundleCreator creator = ConfigBundleCreator(config) @@ -983,7 +983,7 @@ def command_bundle(args: ArgsProtocol, config: ConfigType) -> int | None: output_path = Path(args.output) else: stem = CORE.config_path.stem - output_path = CORE.config_dir / f"{stem}.bundle.tar.gz" + output_path = CORE.config_dir / f"{stem}{BUNDLE_EXTENSION}" output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_bytes(result.data) @@ -1674,7 +1674,7 @@ def run_esphome(argv): _LOGGER.warning("Skipping secrets file %s", conf_path) return 0 - # Bundle support: if the configuration is a .tar.gz bundle, extract it + # Bundle support: if the configuration is a .esphomebundle, extract it # and rewrite conf_path to the extracted YAML config. from esphome.bundle import is_bundle_path, prepare_bundle_for_compile diff --git a/esphome/bundle.py b/esphome/bundle.py index a74e13858e..bdda7e62b4 100644 --- a/esphome/bundle.py +++ b/esphome/bundle.py @@ -28,6 +28,10 @@ MANIFEST_FILENAME = "manifest.json" CURRENT_MANIFEST_VERSION = 1 MAX_DECOMPRESSED_SIZE = 500 * 1024 * 1024 # 500 MB +# Directories preserved across bundle extractions (build caches) +_PRESERVE_DIRS = (".esphome", ".pioenvs", ".pio") +_BUNDLE_STAGING_DIR = ".bundle_staging" + # String prefixes that are never local file paths _NON_PATH_PREFIXES = ("http://", "https://", "ftp://", "mdi:", "<") @@ -374,7 +378,7 @@ class ConfigBundleCreator: continue filtered = {k: v for k, v in all_secrets.items() if k in used_keys} if filtered: - data = yaml_util.dump(filtered).encode("utf-8") + data = yaml_util.dump(filtered, show_secrets=True).encode("utf-8") result[rel_path] = data return result @@ -552,10 +556,12 @@ def _validate_tar_members(tar: tarfile.TarFile, target_dir: Path) -> None: ) +BUNDLE_EXTENSION = ".esphomebundle.tar.gz" + + def is_bundle_path(path: Path) -> bool: """Check if a path looks like a bundle file.""" - name = path.name.lower() - return name.endswith(".tar.gz") or name.endswith(".tgz") + return path.name.lower().endswith(BUNDLE_EXTENSION) def _add_bytes_to_tar(tar: tarfile.TarFile, name: str, data: bytes) -> None: @@ -581,9 +587,10 @@ def _resolve_include_path(include_path: Any) -> Path | None: def _default_target_dir(bundle_path: Path) -> Path: """Compute the default extraction directory for a bundle.""" - return bundle_path.parent / bundle_path.name.removesuffix(".tar.gz").removesuffix( - ".gz" - ) + name = bundle_path.name + if name.lower().endswith(BUNDLE_EXTENSION): + name = name[: -len(BUNDLE_EXTENSION)] + return bundle_path.parent / name def _restore_preserved_dirs(preserved: dict[str, Path], target_dir: Path) -> None: @@ -622,13 +629,11 @@ def prepare_bundle_for_compile( target_dir = target_dir.resolve() target_dir.mkdir(parents=True, exist_ok=True) - # Identify directories to preserve (build caches) - preserve_dirs = [".esphome", ".pioenvs", ".pio"] preserved: dict[str, Path] = {} # Temporarily move preserved dirs out of the way - staging = target_dir / ".bundle_staging" - for dirname in preserve_dirs: + staging = target_dir / _BUNDLE_STAGING_DIR + for dirname in _PRESERVE_DIRS: src = target_dir / dirname if src.is_dir(): dst = staging / dirname @@ -639,7 +644,7 @@ def prepare_bundle_for_compile( try: # Clean non-preserved content and extract fresh for item in target_dir.iterdir(): - if item.name == ".bundle_staging": + if item.name == _BUNDLE_STAGING_DIR: continue if item.is_dir(): shutil.rmtree(item) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index bba4bbf487..b88ed4fa1d 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -435,10 +435,10 @@ def _load_yaml_internal(fname: Path) -> Any: raise EsphomeError(f"Error reading file {fname}: {err}") from err -def parse_yaml( - file_name: Path, file_handle: TextIOWrapper, yaml_loader=_load_yaml_internal -) -> Any: +def parse_yaml(file_name: Path, file_handle: TextIOWrapper, yaml_loader=None) -> Any: """Parse a YAML file.""" + if yaml_loader is None: + yaml_loader = _load_yaml_internal try: return _load_yaml_internal_with_type( ESPHomeLoader, file_name, file_handle, yaml_loader