diff --git a/Formula/wtcraft.rb b/Formula/wtcraft.rb index 00d171f..7c6b226 100644 --- a/Formula/wtcraft.rb +++ b/Formula/wtcraft.rb @@ -3,7 +3,7 @@ class Wtcraft < Formula homepage "https://github.com/zywkloo/wtcraft" # Update url + sha256 for each release: # curl -sL | shasum -a 256 - url "https://github.com/zywkloo/wtcraft/archive/refs/tags/v0.4.1.tar.gz" + url "https://github.com/zywkloo/wtcraft/archive/refs/tags/v0.4.2.tar.gz" sha256 "eb5fc8483016211fcd78f6869d73bf42421d59be56b31a0eebd7b3bc7fe23239" license "Apache-2.0" head "https://github.com/zywkloo/wtcraft.git", branch: "main" @@ -23,6 +23,7 @@ def install (bin/"wtcraft").write <<~SH #!/usr/bin/env bash export WTCRAFT_TEMPLATE_DIR="#{pkgshare}/templates" + export WTCRAFT_VERSION="#{version}" exec "#{libexec}/wtcraft-real" "$@" SH end diff --git a/package.json b/package.json index 71d3034..9e512ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wtcraft", - "version": "0.4.1", + "version": "0.4.2", "description": "Zero-trust containment and governance for AI-generated code.", "license": "Apache-2.0", "bin": { diff --git a/pyproject.toml b/pyproject.toml index 2f9f103..c853b85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "wtcraft" -version = "0.4.1" +version = "0.4.2" description = "A lightweight, git-native scaffolding for bounded multi-agent coding" license = { text = "Apache-2.0" } requires-python = ">=3.7" diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 0000000..5b74052 --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# +# Single entry point for bumping the wtcraft version. +# +# The version unavoidably lives in three build manifests (each build system +# reads its own): pyproject.toml, package.json, and the Homebrew formula's +# release tag. This script is the one command that keeps them in sync, so the +# version is bumped from a single place instead of edited by hand in three. +# +# The Python package (__init__.py) and the installed shell (read_cli_version) +# derive their version at runtime, so they are not edited here. +# +# Usage: scripts/bump-version.sh e.g. scripts/bump-version.sh 0.4.2 +# +set -euo pipefail + +new="${1:-}" +case "$new" in + "") echo "usage: bump-version.sh (e.g. 0.4.2)" >&2; exit 1 ;; + *[!0-9.]*) echo "version must contain only digits and dots: $new" >&2; exit 1 ;; +esac + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# pyproject.toml: version = "x" +sed -i.bak -E "s/^version = \"[^\"]*\"/version = \"${new}\"/" "${root}/pyproject.toml" +# package.json: "version": "x" (the sole top-level version key) +sed -i.bak -E "s/(\"version\"[[:space:]]*:[[:space:]]*\")[^\"]*(\")/\1${new}\2/" "${root}/package.json" +# Formula: .../tags/v.tar.gz (Homebrew derives #{version} from this) +sed -i.bak -E "s#(archive/refs/tags/v)[0-9][0-9.]*(\.tar\.gz)#\1${new}\2#" "${root}/Formula/wtcraft.rb" + +rm -f "${root}/pyproject.toml.bak" "${root}/package.json.bak" "${root}/Formula/wtcraft.rb.bak" + +echo "Bumped wtcraft to ${new} in:" +echo " pyproject.toml" +echo " package.json" +echo " Formula/wtcraft.rb (release tag)" +echo +echo "Still manual: Formula sha256 must be updated for the new tarball:" +echo " curl -sL https://github.com/zywkloo/wtcraft/archive/refs/tags/v${new}.tar.gz | shasum -a 256" diff --git a/scripts/wtcraft b/scripts/wtcraft index 23c7629..13c231d 100755 --- a/scripts/wtcraft +++ b/scripts/wtcraft @@ -277,6 +277,13 @@ die() { } read_cli_version() { + # Installers stamp WTCRAFT_VERSION (brew wrapper, pip _cli.py) so the version + # survives even when no manifest sits beside the installed script. + if [ -n "${WTCRAFT_VERSION:-}" ]; then + printf '%s' "${WTCRAFT_VERSION}" + return 0 + fi + # In-repo development: read it straight from the manifest next to the script. if [ -f "${ROOT_DIR}/package.json" ]; then awk -F'"' '/"version"[[:space:]]*:/ { print $4; exit }' "${ROOT_DIR}/package.json" return 0 diff --git a/src/wtcraft/__init__.py b/src/wtcraft/__init__.py index edc294a..7befe40 100644 --- a/src/wtcraft/__init__.py +++ b/src/wtcraft/__init__.py @@ -1,2 +1,12 @@ # wtcraft — git-native harness helper -__version__ = "0.3.8" +# Derive the version from installed package metadata so there is no second +# hardcoded literal to drift out of sync with pyproject.toml. +try: + from importlib.metadata import PackageNotFoundError, version + + try: + __version__ = version("wtcraft") + except PackageNotFoundError: + __version__ = "0.0.0+source" +except ImportError: # Python < 3.8 + __version__ = "0.0.0+source" diff --git a/src/wtcraft/_cli.py b/src/wtcraft/_cli.py index 9f11d13..c3faf6e 100644 --- a/src/wtcraft/_cli.py +++ b/src/wtcraft/_cli.py @@ -35,6 +35,20 @@ def main() -> None: env = os.environ.copy() env["WTCRAFT_TEMPLATE_DIR"] = tmpl_dir + # Stamp the resolved package version so the shell reports it even when no + # manifest sits beside the installed script (the pip/pipx layout). The value + # comes from the installed package metadata, the single source of truth. + if "WTCRAFT_VERSION" not in env: + try: + from importlib.metadata import PackageNotFoundError, version + + try: + env["WTCRAFT_VERSION"] = version("wtcraft") + except PackageNotFoundError: + pass + except ImportError: # Python < 3.8: leave it to the shell's fallback + pass + bash = shutil.which("bash") or r"C:\Program Files\Git\bin\bash.exe" os.execve(bash, [bash, shell_path] + sys.argv[1:], env) diff --git a/tests/e2e_version.sh b/tests/e2e_version.sh new file mode 100755 index 0000000..9006c2c --- /dev/null +++ b/tests/e2e_version.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# +# Version resolution: env stamp wins, in-repo reads the manifest, and a +# manifest-less install degrades to "unknown" instead of erroring. +# +source "$(dirname "${BASH_SOURCE[0]}")/framework.sh" + +fail() { echo "FAIL: $*" >&2; exit 1; } + +# 1. Installer-stamped WTCRAFT_VERSION wins. +out="$(WTCRAFT_VERSION="9.9.9-test" bash "$CLI" --version)" +[ "$out" = "9.9.9-test" ] || fail "env stamp: expected 9.9.9-test, got '$out'" + +# 2. In-repo (no env): reads a real manifest version, never "unknown". +out="$(env -u WTCRAFT_VERSION bash "$CLI" --version)" +case "$out" in + unknown | "") fail "in-repo --version should read a manifest, got '$out'" ;; +esac + +# 3. Manifest-less install (script copied away from its manifests, no env): +# degrades to "unknown" cleanly. Mirrors the brew/pip-without-stamp layout. +base="$(mktemp -d)" +mkdir -p "${base}/sub" +cp "$CLI" "${base}/sub/wtcraft" +out="$(env -u WTCRAFT_VERSION bash "${base}/sub/wtcraft" --version)" +rm -rf "$base" +[ "$out" = "unknown" ] || fail "manifest-less --version should be 'unknown', got '$out'" + +echo "Version resolution tests passed." diff --git a/tests/run_all.sh b/tests/run_all.sh index 97a2760..5e4c317 100755 --- a/tests/run_all.sh +++ b/tests/run_all.sh @@ -10,6 +10,7 @@ bash "${SCRIPT_DIR}/unit_awk.sh" bash "${SCRIPT_DIR}/e2e_init_patch.sh" bash "${SCRIPT_DIR}/e2e_lifecycle.sh" bash "${SCRIPT_DIR}/e2e_doctor_migrate.sh" +bash "${SCRIPT_DIR}/e2e_version.sh" # Integration tests require npm and python3 (skip gracefully if unavailable) if command -v npm >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then