Skip to content

Commit 94f78f1

Browse files
committed
Sync wheel version to git tag; preserve _bin/.gitignore on build
The release version now derives from the git tag: release.yml extracts X.Y.Z from the vX.Y.Z tag, verifies it matches package.json (fails fast on mismatch), and passes it as $PKG_VERSION into build_wheels.sh, which stamps it into __init__.py -- hatch's single source of truth (pyproject declares a dynamic version). This keeps the GitHub Release tag, the PyPI wheel version, and the npm version in lockstep instead of the previously hardcoded 0.1.0. Also make build_wheels.sh clean only the compiled binary from _bin/, preserving the tracked .gitignore so a local build leaves the tree pristine.
1 parent f57410f commit 94f78f1

4 files changed

Lines changed: 75 additions & 11 deletions

File tree

.github/workflows/release.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ jobs:
2525
- name: Check out code
2626
uses: actions/checkout@v4
2727

28+
# Derive the release version from the tag (vX.Y.Z -> X.Y.Z) and require it
29+
# to match package.json. This keeps the PyPI wheel version, the GitHub
30+
# Release tag, and the npm version in lockstep -- a mismatch fails fast
31+
# rather than silently publishing the wrong version to PyPI.
32+
- name: Determine and verify version
33+
id: ver
34+
run: |
35+
pkgjson="$(node -p "require('./package.json').version")"
36+
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
37+
version="${GITHUB_REF#refs/tags/v}"
38+
if [[ "$version" != "$pkgjson" ]]; then
39+
echo "::error::Tag version ($version) != package.json version ($pkgjson). Bump package.json or fix the tag."
40+
exit 1
41+
fi
42+
else
43+
version="$pkgjson" # workflow_dispatch: no tag, fall back to package.json
44+
fi
45+
echo "version=$version" >> "$GITHUB_OUTPUT"
46+
echo ">>> Releasing version $version"
47+
2848
- name: Set up Bun
2949
uses: oven-sh/setup-bun@v2
3050
with:
@@ -64,6 +84,8 @@ jobs:
6484

6585
- name: Build platform wheels (cross-compiles every target via Bun)
6686
working-directory: packaging/python
87+
env:
88+
PKG_VERSION: ${{ steps.ver.outputs.version }}
6789
run: ./build_wheels.sh
6890

6991
- name: Extract raw binaries from wheels (for GitHub Release)

packaging/python/README.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,26 @@ matrix.
3131

3232
## Versioning
3333

34-
Keep the version in lockstep across three places:
34+
The released version comes from the **git tag**. A push of `vX.Y.Z` triggers the
35+
release workflow, which derives `X.Y.Z` from the tag, verifies it matches this
36+
repo's `package.json` `version` (failing fast on mismatch), and stamps it into
37+
`__init__.py` via `$PKG_VERSION` — hatch reads `__version__` as the wheel version
38+
(`pyproject.toml` declares `dynamic = ["version"]`). So the GitHub Release tag, the
39+
PyPI wheel version, and the npm `package.json` version are always in lockstep.
3540

36-
- this repo's `package.json` (`version`)
37-
- `packaging/python/pyproject.toml` (`project.version`) and `__init__.py` (`__version__`)
38-
- the python-sdk pin in `pyproject.toml`: `[tool.backend-versions] codeanalyzer-typescript`
39-
and the `dependencies` entry `codeanalyzer-typescript==<version>`
41+
To cut a release: bump `package.json` `version`, then push the matching tag, e.g.
42+
43+
```bash
44+
npm version 0.2.0 --no-git-tag-version # or edit package.json
45+
git commit -am "Release v0.2.0" && git tag v0.2.0 && git push --tags
46+
```
47+
48+
For a **local** wheel build, override the fallback version explicitly:
49+
`PKG_VERSION=0.2.0 ./build_wheels.sh`.
50+
51+
One thing still tracked by hand: the python-sdk pin — `[tool.backend-versions]
52+
codeanalyzer-typescript` and the `dependencies` entry
53+
`codeanalyzer-typescript==<version>` — must be bumped to consume a new release.
4054

4155
## SDK integration
4256

packaging/python/build_wheels.sh

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,33 @@ set -euo pipefail
1919

2020
HERE="$(cd "$(dirname "$0")" && pwd)"
2121
REPO_ROOT="$(cd "$HERE/../.." && pwd)" # codeanalyzer-ts repo root (has src/index.ts)
22-
PKG_VERSION="0.1.0"
22+
# Version comes from the environment (the release workflow sets it from the git
23+
# tag); the literal is only a local-dev fallback. It is written into __init__.py,
24+
# which is hatch's single source of truth for the wheel version.
25+
PKG_VERSION="${PKG_VERSION:-0.1.0}"
2326
WHEEL_STEM="codeanalyzer_typescript-${PKG_VERSION}-py3-none-any.whl"
2427
BIN_DIR="$HERE/src/codeanalyzer_typescript/_bin"
28+
INIT_PY="$HERE/src/codeanalyzer_typescript/__init__.py"
29+
30+
# Remove built binaries from _bin/ but keep the tracked .gitignore (and the dir),
31+
# so a local build leaves the working tree pristine.
32+
clean_bin() { mkdir -p "$BIN_DIR"; find "$BIN_DIR" -mindepth 1 ! -name '.gitignore' -delete; }
33+
34+
# Stamp $PKG_VERSION into __init__.py for the build, restoring the original on
35+
# exit so the working tree stays pristine (mirrors the _bin cleanup below).
36+
ORIG_INIT="$(cat "$INIT_PY")" # $(...) strips the trailing newline; restore re-adds it
37+
restore_init() { printf '%s\n' "$ORIG_INIT" > "$INIT_PY"; }
38+
trap restore_init EXIT
39+
python - "$INIT_PY" "$PKG_VERSION" <<'PY'
40+
import re, sys
41+
path, version = sys.argv[1], sys.argv[2]
42+
text = open(path).read()
43+
new, n = re.subn(r'__version__ = "[^"]*"', f'__version__ = "{version}"', text)
44+
if n != 1:
45+
raise SystemExit(f"expected exactly one __version__ assignment in {path}, found {n}")
46+
open(path, "w").write(new)
47+
print(f">>> stamped __version__ = {version}")
48+
PY
2549

2650
# "bun --target" : "wheel platform tag"
2751
TARGETS=(
@@ -46,8 +70,7 @@ for entry in "${TARGETS[@]}"; do
4670

4771
echo ">>> [$target] compiling -> wheel ($plat)"
4872

49-
rm -rf "$BIN_DIR"
50-
mkdir -p "$BIN_DIR"
73+
clean_bin
5174

5275
( cd "$REPO_ROOT" && bun build ./src/index.ts --compile --target="$target" \
5376
--outfile "$BIN_DIR/codeanalyzer-typescript$ext" )
@@ -58,8 +81,7 @@ for entry in "${TARGETS[@]}"; do
5881
done
5982

6083
# Clean the working binary so the tree stays pristine.
61-
rm -rf "$BIN_DIR"
62-
mkdir -p "$BIN_DIR"
84+
clean_bin
6385

6486
echo
6587
echo ">>> Built wheels:"

packaging/python/pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "codeanalyzer-typescript"
7-
version = "0.1.0"
7+
dynamic = ["version"]
88
description = "Prebuilt codeanalyzer-typescript backend binary for CLDK (codellm-devkit)."
99
readme = "README.md"
1010
requires-python = ">=3.8"
@@ -24,6 +24,12 @@ classifiers = [
2424
Homepage = "https://codellm-devkit.info"
2525
Repository = "https://github.com/codellm-devkit/codeanalyzer-ts"
2626

27+
# Single source of truth for the package version: __init__.py's __version__.
28+
# build_wheels.sh overwrites it from $PKG_VERSION (set to the git tag in CI), so a
29+
# tag of vX.Y.Z always produces a codeanalyzer-typescript==X.Y.Z wheel.
30+
[tool.hatch.version]
31+
path = "src/codeanalyzer_typescript/__init__.py"
32+
2733
# The compiled binary lives under _bin/ and is gitignored (it is produced per
2834
# platform at build time). `artifacts` re-includes it in the wheel anyway --
2935
# this mirrors how the Java jar is force-included in the python-sdk wheel.

0 commit comments

Comments
 (0)