From 45a937e14bba93bac2343adc43a39797ede5d2e7 Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Mon, 6 Apr 2026 20:23:12 -0400 Subject: [PATCH 1/2] fix(windows): move helpers from sh to py --- {{cookiecutter.project_name}}/Taskfile.yml | 17 ++----- .../scripts/clean.py | 49 +++++++++++++++++++ .../scripts/get_epoch.py | 6 +++ .../scripts/get_epoch.sh | 4 -- .../scripts/get_platform.py | 28 +++++++++++ .../scripts/get_platform.sh | 19 ------- 6 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 {{cookiecutter.project_name}}/scripts/clean.py create mode 100644 {{cookiecutter.project_name}}/scripts/get_epoch.py delete mode 100755 {{cookiecutter.project_name}}/scripts/get_epoch.sh create mode 100644 {{cookiecutter.project_name}}/scripts/get_platform.py delete mode 100755 {{cookiecutter.project_name}}/scripts/get_platform.sh diff --git a/{{cookiecutter.project_name}}/Taskfile.yml b/{{cookiecutter.project_name}}/Taskfile.yml index c218022..b68c083 100644 --- a/{{cookiecutter.project_name}}/Taskfile.yml +++ b/{{cookiecutter.project_name}}/Taskfile.yml @@ -18,7 +18,7 @@ vars: VERSION: sh: "{{ '{{.RUN_SCRIPT}}' }} python -c \"import sys; sys.path.insert(0, 'src'); from {{ '{{.PROJECT_SLUG}}' }} import __version__; print(__version__)\"" LOCAL_PLATFORM: - sh: "bash {{ '{{.SCRIPTS_DIR}}' }}/get_platform.sh" + sh: "{{ '{{.RUN_SCRIPT}}' }} {{ '{{.SCRIPTS_DIR}}' }}/get_platform.py" # Use PLATFORM if specified, otherwise use LOCAL_PLATFORM PLATFORM: '{{ '{{if .PLATFORM}}' }}{{ '{{.PLATFORM}}' }}{{ '{{else}}' }}{{ '{{.LOCAL_PLATFORM}}' }}{{ '{{end}}' }}' # Output redirect based on CI environment @@ -88,7 +88,7 @@ tasks: TIMESTAMP: sh: '{{ '{{.RUN_SCRIPT}}' }} {{ '{{.SCRIPTS_DIR}}' }}/get_rfc3339_timestamp.py' EPOCH: - sh: 'bash {{ '{{.SCRIPTS_DIR}}' }}/get_epoch.sh' + sh: '{{ '{{.RUN_SCRIPT}}' }} {{ '{{.SCRIPTS_DIR}}' }}/get_epoch.py' COMMIT_HASH: sh: git rev-parse HEAD BUILD_PLATFORM: '{{ '{{if eq .PLATFORM "all"}}' }}{{ '{{.SUPPORTED_PLATFORMS}}' }}{{ '{{else if .PLATFORM}}' }}{{ '{{.PLATFORM}}' }}{{ '{{else}}' }}{{ '{{.LOCAL_PLATFORM}}' }}{{ '{{end}}' }}' @@ -187,18 +187,7 @@ tasks: clean: desc: Clean up build artifacts, cache files/directories, temp files, etc. cmds: - - rm -rf .pytest_cache - - rm -rf htmlcov - - rm -rf .coverage - - rm -rf dist - - rm -rf build - - rm -rf *.egg-info - - rm -f sbom.*.json - - rm -f vulns.*.json - - rm -f license-check.*.json - - rm -f {{ cookiecutter.github_org }}_{{ cookiecutter.project_slug }}_*_*.tar - - find . -type d -name __pycache__ -exec rm -rf {} + || true - - find . -type f -name '*.pyc' -delete || true + - '{{ '{{.RUN_SCRIPT}}' }} {{ '{{.SCRIPTS_DIR}}' }}/clean.py' release: desc: Cut a project release diff --git a/{{cookiecutter.project_name}}/scripts/clean.py b/{{cookiecutter.project_name}}/scripts/clean.py new file mode 100644 index 0000000..dbd5f41 --- /dev/null +++ b/{{cookiecutter.project_name}}/scripts/clean.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Cross-platform cleanup of build artifacts, cache files, and temp files.""" + +import glob +import shutil +import sys +from pathlib import Path + + +def main() -> int: + """Remove build artifacts, cache directories, and temporary files.""" + root = Path(".") + + # Paths to remove (may be files or directories) + for name in [".pytest_cache", "htmlcov", ".coverage", "dist", "build"]: + path = root / name + if path.is_dir(): + shutil.rmtree(path) + elif path.is_file(): + path.unlink() + + # Glob patterns for directories + for path in glob.glob("*.egg-info"): + shutil.rmtree(path) + + # Glob patterns for files + for pattern in [ + "sbom.*.json", + "vulns.*.json", + "license-check.*.json", + "{{ cookiecutter.github_org }}_{{ cookiecutter.project_slug }}_*_*.tar", + ]: + for path in glob.glob(pattern): + Path(path).unlink() + + # Recursively remove __pycache__ directories + for path in root.rglob("__pycache__"): + if path.is_dir(): + shutil.rmtree(path) + + # Recursively remove .pyc files + for path in root.rglob("*.pyc"): + path.unlink() + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/{{cookiecutter.project_name}}/scripts/get_epoch.py b/{{cookiecutter.project_name}}/scripts/get_epoch.py new file mode 100644 index 0000000..9bd5159 --- /dev/null +++ b/{{cookiecutter.project_name}}/scripts/get_epoch.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +"""Get the current Unix epoch timestamp.""" + +import time + +print(int(time.time())) diff --git a/{{cookiecutter.project_name}}/scripts/get_epoch.sh b/{{cookiecutter.project_name}}/scripts/get_epoch.sh deleted file mode 100755 index 5bf579e..0000000 --- a/{{cookiecutter.project_name}}/scripts/get_epoch.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -date +%s diff --git a/{{cookiecutter.project_name}}/scripts/get_platform.py b/{{cookiecutter.project_name}}/scripts/get_platform.py new file mode 100644 index 0000000..c1abfbd --- /dev/null +++ b/{{cookiecutter.project_name}}/scripts/get_platform.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""Get the Docker platform string for the current machine. + +Always uses 'linux' as the OS since container builds target Linux. +""" + +import platform +import sys + +# Always use linux for container builds +os_name = "linux" +machine = platform.machine().lower() + +# Inspired by https://github.com/containerd/containerd/blob/e0912c068b131b33798ae45fd447a1624a6faf0a/platforms/database.go#L76 +arch_map = { + # AMD64 + "x86_64": "amd64", + "amd64": "amd64", + # ARM64 + "aarch64": "arm64", + "arm64": "arm64", +} + +if machine not in arch_map: + print(f"Unsupported architecture: {machine}", file=sys.stderr) + sys.exit(1) + +print(f"{os_name}/{arch_map[machine]}") diff --git a/{{cookiecutter.project_name}}/scripts/get_platform.sh b/{{cookiecutter.project_name}}/scripts/get_platform.sh deleted file mode 100755 index 9010676..0000000 --- a/{{cookiecutter.project_name}}/scripts/get_platform.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Always use linux for container builds -os="linux" -arch="$(uname -m)" - -# Inspired by https://github.com/containerd/containerd/blob/e0912c068b131b33798ae45fd447a1624a6faf0a/platforms/database.go#L76 -case ${arch} in - # AMD64 - x86_64) echo "${os}/amd64" ;; - amd64) echo "${os}/amd64" ;; - - # ARM64 - aarch64) echo "${os}/arm64" ;; - arm64) echo "${os}/arm64" ;; - - *) echo "Unsupported architecture: $arch" && exit 1 ;; -esac From e391af059d770fb0d146e4953b03a6e610e285b6 Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Mon, 6 Apr 2026 21:09:44 -0400 Subject: [PATCH 2/2] fix(ci): add executable bit to new Python scripts The pre-commit hook "check that scripts with shebangs are executable" was failing because the new clean.py, get_epoch.py, and get_platform.py scripts had shebangs but were not marked executable. Co-Authored-By: Claude Opus 4.6 (1M context) --- {{cookiecutter.project_name}}/scripts/clean.py | 0 {{cookiecutter.project_name}}/scripts/get_epoch.py | 0 {{cookiecutter.project_name}}/scripts/get_platform.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 {{cookiecutter.project_name}}/scripts/clean.py mode change 100644 => 100755 {{cookiecutter.project_name}}/scripts/get_epoch.py mode change 100644 => 100755 {{cookiecutter.project_name}}/scripts/get_platform.py diff --git a/{{cookiecutter.project_name}}/scripts/clean.py b/{{cookiecutter.project_name}}/scripts/clean.py old mode 100644 new mode 100755 diff --git a/{{cookiecutter.project_name}}/scripts/get_epoch.py b/{{cookiecutter.project_name}}/scripts/get_epoch.py old mode 100644 new mode 100755 diff --git a/{{cookiecutter.project_name}}/scripts/get_platform.py b/{{cookiecutter.project_name}}/scripts/get_platform.py old mode 100644 new mode 100755