From 5df2e57547079411d407dacdd936a2720db1c35a Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Mon, 2 Mar 2026 17:38:28 +0000 Subject: [PATCH 1/2] Refactor script path resolution and cleanup in `resolve_version_via_semantic_release` function This commit enhances the logic for locating the `get-next-version.cjs` script by ensuring a more structured approach to checking both the project root and installed package resources. It introduces a temporary file context for cleanup, ensuring resources are managed properly even on early returns. These changes improve the robustness and reliability of the version resolution process. --- .../python_package_folder.py | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/src/python_package_folder/python_package_folder.py b/src/python_package_folder/python_package_folder.py index e7daf2f..dfe77da 100644 --- a/src/python_package_folder/python_package_folder.py +++ b/src/python_package_folder/python_package_folder.py @@ -45,48 +45,49 @@ def resolve_version_via_semantic_release( # - For normal installs: direct file path # - For zip/pex installs: extract to temporary file using as_file() - # First, try project root (development) - dev_script = project_root / "scripts" / "get-next-version.cjs" - if dev_script.exists(): - script_path = dev_script - temp_script_context = None - else: - # Try to locate script in installed package using importlib.resources - script_path = None - temp_script_context = None - try: - package = resources.files("python_package_folder") - script_resource = package / "scripts" / "get-next-version.cjs" - if script_resource.is_file(): - # Try direct path conversion first (normal file system install) - try: - script_path_candidate = Path(str(script_resource)) - if script_path_candidate.exists(): - script_path = script_path_candidate - except (TypeError, ValueError): - pass - - # If direct path didn't work, try as_file() for zip/pex installs - if script_path is None: + # Track temporary file context for cleanup + temp_script_context = None + + try: + # First, try project root (development) + dev_script = project_root / "scripts" / "get-next-version.cjs" + if dev_script.exists(): + script_path = dev_script + else: + # Try to locate script in installed package using importlib.resources + script_path = None + try: + package = resources.files("python_package_folder") + script_resource = package / "scripts" / "get-next-version.cjs" + if script_resource.is_file(): + # Try direct path conversion first (normal file system install) try: - temp_script_context = resources.as_file(script_resource) - script_path = temp_script_context.__enter__() - except (TypeError, ValueError, OSError): + script_path_candidate = Path(str(script_resource)) + if script_path_candidate.exists(): + script_path = script_path_candidate + except (TypeError, ValueError): pass - except (ImportError, ModuleNotFoundError, TypeError, AttributeError, OSError): - pass - # Fallback: try relative to package directory - if script_path is None: - package_dir = Path(__file__).parent - fallback_script = package_dir / "scripts" / "get-next-version.cjs" - if fallback_script.exists(): - script_path = fallback_script + # If direct path didn't work, try as_file() for zip/pex installs + if script_path is None: + try: + temp_script_context = resources.as_file(script_resource) + script_path = temp_script_context.__enter__() + except (TypeError, ValueError, OSError): + pass + except (ImportError, ModuleNotFoundError, TypeError, AttributeError, OSError): + pass - if not script_path: - return None + # Fallback: try relative to package directory + if script_path is None: + package_dir = Path(__file__).parent + fallback_script = package_dir / "scripts" / "get-next-version.cjs" + if fallback_script.exists(): + script_path = fallback_script + + if not script_path: + return None - try: # Build command arguments cmd = ["node", str(script_path), str(project_root)] if subfolder_path and package_name: @@ -142,6 +143,7 @@ def resolve_version_via_semantic_release( return None finally: # Clean up temporary file if we extracted from zip/pex + # This must be at function level to ensure cleanup even on early return if temp_script_context is not None: try: temp_script_context.__exit__(None, None, None) From d6637dd238fc564787e01d056b48ca4eb41a0aa8 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Mon, 2 Mar 2026 18:47:00 +0000 Subject: [PATCH 2/2] Update `pyproject.toml` to use a build hook for script inclusion This commit modifies the script inclusion method in `pyproject.toml` by replacing the `force-include` directive with a build hook. This change allows for automatic inclusion of all files in the scripts directory, reducing duplication and improving sustainability for future additions. --- pyproject.toml | 7 ++-- src/python_package_folder/_hatch_build.py | 39 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 src/python_package_folder/_hatch_build.py diff --git a/pyproject.toml b/pyproject.toml index 6329ec2..50229fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,9 +85,10 @@ bump = true [tool.hatch.build.targets.wheel] # The source location for the package. packages = ["src/python_package_folder"] -# Force-include the scripts directory (non-Python files) -# Place scripts inside the package directory so importlib.resources can find them -force-include = { "src/python_package_folder/scripts" = "python_package_folder/scripts" } +# Using a build hook to automatically include all files in the scripts directory +# This avoids duplicates while being sustainable for future file additions +[tool.hatch.build.hooks.custom] +path = "src/python_package_folder/_hatch_build.py" # ---- Settings ---- diff --git a/src/python_package_folder/_hatch_build.py b/src/python_package_folder/_hatch_build.py new file mode 100644 index 0000000..4f487a1 --- /dev/null +++ b/src/python_package_folder/_hatch_build.py @@ -0,0 +1,39 @@ +""" +Hatch build hook to automatically include all files from the scripts directory. + +This hook ensures all non-Python files in the scripts directory are included +in the wheel without creating duplicates, and automatically includes any new +files added to the directory without requiring manual configuration updates. +""" + +from pathlib import Path +from typing import Any + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +class CustomBuildHook(BuildHookInterface): + """Build hook to include all files from the scripts directory.""" + + def initialize(self, version: str, build_data: dict[str, Any]) -> None: + """Initialize the build hook and add scripts directory files.""" + # Get the source directory for the package + source_dir = Path(self.root) / "src" / "python_package_folder" + scripts_dir = source_dir / "scripts" + + # If scripts directory exists, include all files from it + if scripts_dir.exists() and scripts_dir.is_dir(): + # Add all files from scripts directory to force-include + # This ensures they're included in the wheel at the correct location + for script_file in scripts_dir.iterdir(): + if script_file.is_file(): + # Calculate relative paths + source_path = script_file.relative_to(self.root) + # Target path inside the wheel package + target_path = f"python_package_folder/scripts/{script_file.name}" + + # Add to force-include (hatchling will handle this) + # We need to add it to build_data['force_include'] + if "force_include" not in build_data: + build_data["force_include"] = {} + build_data["force_include"][str(source_path)] = target_path