From 6c210f792f219917be5a3bfcbd439b906ea1b611 Mon Sep 17 00:00:00 2001 From: Suriyakumarvijayanayagam Date: Sat, 19 Sep 2026 12:44:29 +0530 Subject: [PATCH] release.sh: the shop-PC package, built the same way every time The previous releases were assembled by hand. This builds the Windows zip from a clean tree - desktop app, agent, setup tool cross-compiled here, the engine as a pure-Python wheel with its source beside it - tags, and publishes to Gitea with notes from a reviewed file. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj --- release.sh | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100755 release.sh diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..18474d0 --- /dev/null +++ b/release.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Build the Windows shop-PC package and publish it as a Gitea release. +# +# ./release.sh v0.4.2 build dist/Behavision-v0.4.2-windows-x64.zip and publish +# PUBLISH=0 ./release.sh v0.4.2 build only +# +# The package is a SOURCE install: the Go binaries are cross-compiled here, the +# engine ships as a pure-Python wheel and behavision-setup.exe builds a venv on +# the shop PC. PyInstaller cannot cross-compile, so a frozen engine needs a +# Windows build machine we do not have; this is what lets a release happen +# from this Mac at all. Layout matches what behavision-setup expects and what +# INSTALL.txt describes. +set -euo pipefail +cd "$(dirname "$0")" +export PATH="$PATH:$HOME/go/bin:/opt/homebrew/bin" + +TAG=${1:?usage: release.sh vX.Y.Z} +REPO_API=https://gitapp.workolik.com/api/v1/repos/Loyaly/Behavision +STAGE=dist/Behavision +ZIP="dist/Behavision-$TAG-windows-x64.zip" + +step() { printf '\n\033[1m%s\033[0m\n' "$*"; } +case "$(git describe --tags --always --dirty)" in *-dirty) echo "refusing to release uncommitted changes" >&2; exit 1;; esac +if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then + [ "$(git rev-parse "$TAG^{}")" = "$(git rev-parse HEAD)" ] || { echo "$TAG exists and is not HEAD" >&2; exit 1; } +fi + +step "1. Desktop app (Wails, pure-Go Windows target)" +(cd desktop/frontend && npm run build >/dev/null) +rm -rf "$STAGE" && mkdir -p "$STAGE/engine-src" +(cd desktop && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath \ + -ldflags "-H windowsgui -s -w -X main.version=$TAG" -o "../$STAGE/Behavision.exe" .) + +step "2. Agent and setup tool" +(cd agent && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath \ + -ldflags "-s -w -X main.version=$TAG" -o "../$STAGE/behavision-agent.exe" . \ + && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath \ + -ldflags "-s -w -X main.version=$TAG" -o "../$STAGE/behavision-setup.exe" ./cmd/behavision-setup) + +step "3. Engine source and wheel" +# The wheel is built with the checkout's own interpreter; requires-python is a +# statement about the SHOP PC, which setup enforces when it finds Python there. +.venv/bin/python -m pip wheel --no-deps --no-build-isolation -q -w "$STAGE/engine-src" . 2>&1 | grep -v "DEPRECATION\|WARNING: Ignoring" || true +ls "$STAGE"/engine-src/behavision-*.whl >/dev/null || { echo "wheel was not built" >&2; exit 1; } +cp pyproject.toml requirements.txt "$STAGE/engine-src/" +mkdir -p "$STAGE/engine-src/config" && cp config/default.yaml "$STAGE/engine-src/config/" +rsync -a --exclude '__pycache__' behavision/ "$STAGE/engine-src/behavision/" +cp installer/INSTALL.txt installer/run-with-lan-head-office.cmd "$STAGE/" + +step "4. Package" +rm -f "$ZIP" && (cd dist && zip -qr "$(basename "$ZIP")" Behavision) && ls -la "$ZIP" | awk '{print " " $5 " bytes " $9}' +unzip -l "$ZIP" | grep -E "Behavision\.exe|agent\.exe|setup\.exe|\.whl|INSTALL" | awk '{print " " $4}' + +[ "${PUBLISH:-1}" = "1" ] || { echo "built, not published"; exit 0; } + +step "5. Tag and publish" +git rev-parse -q --verify "refs/tags/$TAG" >/dev/null || git tag -a "$TAG" -m "$TAG" +git push -q origin "$TAG" +# The notes come from a file so they are reviewed, not typed into a shell. +NOTES=${NOTES:-dist/RELEASE-NOTES-$TAG.md} +[ -f "$NOTES" ] || { echo "write the release notes to $NOTES first" >&2; exit 1; } +# Same credential git pushes with; Gitea accepts it as Basic auth for the API. +CRED=$(printf 'protocol=https\nhost=gitapp.workolik.com\n' | git credential fill) +USER=$(printf '%s' "$CRED" | sed -n 's/^username=//p'); PASS=$(printf '%s' "$CRED" | sed -n 's/^password=//p') +BODY=$(python3 -c 'import json,sys;print(json.dumps({"tag_name":sys.argv[1],"name":sys.argv[2],"body":open(sys.argv[3]).read(),"prerelease":True}))' "$TAG" "$TAG — $(head -1 "$NOTES" | sed 's/^#* *//')" "$NOTES") +REL=$(curl -sS -u "$USER:$PASS" -H 'content-type: application/json' -d "$BODY" "$REPO_API/releases") +ID=$(printf '%s' "$REL" | python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])') +curl -sS -u "$USER:$PASS" -F "attachment=@$ZIP" "$REPO_API/releases/$ID/assets?name=$(basename "$ZIP")" >/dev/null +echo " published: https://gitapp.workolik.com/Loyaly/Behavision/releases/tag/$TAG"