From 704a3d8a87caf30058c9fde2f37e722abfef3888 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Tue, 31 Mar 2026 22:52:14 -0700 Subject: [PATCH] Updating changelog + build for 3.6.6 --- CHANGELOG.md | 4 + frontend/package.json | 2 +- pyproject.toml | 2 +- scripts/build/create_github_release.sh | 106 +++++++++ scripts/build/extract_release_notes.sh | 54 +++++ scripts/build/package_release_artifact.sh | 111 ++++++++++ scripts/build/publish.sh | 249 ++++++++-------------- scripts/build/push_docker_multiarch.sh | 79 +++++++ scripts/build/release_common.sh | 93 ++++++++ uv.lock | 2 +- 10 files changed, 544 insertions(+), 158 deletions(-) create mode 100644 scripts/build/create_github_release.sh create mode 100644 scripts/build/extract_release_notes.sh create mode 100644 scripts/build/package_release_artifact.sh create mode 100644 scripts/build/push_docker_multiarch.sh create mode 100644 scripts/build/release_common.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index af7c4ca..e0a2e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [3.6.6] - 2026-03-31 + +* Misc: Please I'm begging for the build scripts to be working now + ## [3.6.5] - 2026-03-31 * Bugfix: Maybe fix problem with publish script diff --git a/frontend/package.json b/frontend/package.json index 728caff..3fb60c9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "remoteterm-meshcore-frontend", "private": true, - "version": "3.6.5", + "version": "3.6.6", "type": "module", "scripts": { "dev": "vite", diff --git a/pyproject.toml b/pyproject.toml index 89fc772..40c0e7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "remoteterm-meshcore" -version = "3.6.5" +version = "3.6.6" description = "RemoteTerm - Web interface for MeshCore radio mesh networks" readme = "README.md" requires-python = ">=3.10" diff --git a/scripts/build/create_github_release.sh b/scripts/build/create_github_release.sh new file mode 100644 index 0000000..7dfae59 --- /dev/null +++ b/scripts/build/create_github_release.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=scripts/build/release_common.sh +source "$SCRIPT_DIR/release_common.sh" + +usage() { + cat <<'EOF' +Usage: scripts/build/create_github_release.sh --version X.Y.Z --asset PATH [options] + +Options: + --version VERSION Release version / tag (required) + --asset PATH Asset to attach; may be specified multiple times + --notes-file PATH Markdown release notes file; defaults to CHANGELOG section + --full-git-hash HASH Commit to tag if the tag does not already exist locally + --title TITLE Release title (default: version) + --help Show this message +EOF +} + +VERSION="" +TITLE="" +NOTES_FILE="" +FULL_GIT_HASH="" +ASSETS=() +TEMP_NOTES_FILE="" + +cleanup() { + if [ -n "$TEMP_NOTES_FILE" ] && [ -f "$TEMP_NOTES_FILE" ]; then + rm -f "$TEMP_NOTES_FILE" + fi +} +trap cleanup EXIT + +while [ $# -gt 0 ]; do + case "$1" in + --version) + VERSION="${2:-}" + shift 2 + ;; + --asset) + ASSETS+=("${2:-}") + shift 2 + ;; + --notes-file) + NOTES_FILE="${2:-}" + shift 2 + ;; + --full-git-hash) + FULL_GIT_HASH="${2:-}" + shift 2 + ;; + --title) + TITLE="${2:-}" + shift 2 + ;; + --help) + usage + exit 0 + ;; + *) + usage >&2 + release_die "Unknown argument: $1" + ;; + esac +done + +[ -n "$VERSION" ] || release_die "--version is required" +[ "${#ASSETS[@]}" -gt 0 ] || release_die "At least one --asset is required" +release_validate_version "$VERSION" + +REPO_ROOT="$(release_repo_root)" +TITLE="${TITLE:-$VERSION}" +FULL_GIT_HASH="${FULL_GIT_HASH:-$(release_resolve_full_hash "$REPO_ROOT")}" + +for asset in "${ASSETS[@]}"; do + [ -f "$asset" ] || release_die "Asset not found: $asset" +done + +if [ -z "$NOTES_FILE" ]; then + TEMP_NOTES_FILE="$(mktemp)" + release_extract_changelog_section "$REPO_ROOT" "$VERSION" "$TEMP_NOTES_FILE" + NOTES_FILE="$TEMP_NOTES_FILE" +fi + +[ -f "$NOTES_FILE" ] || release_die "Notes file not found: $NOTES_FILE" + +if ! git -C "$REPO_ROOT" rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then + echo "[create_github_release] Creating local tag $VERSION at $FULL_GIT_HASH..." >&2 + git -C "$REPO_ROOT" tag -a "$VERSION" "$FULL_GIT_HASH" -F "$NOTES_FILE" +fi + +if ! git -C "$REPO_ROOT" ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then + echo "[create_github_release] Pushing tag $VERSION to origin..." >&2 + git -C "$REPO_ROOT" push origin "$VERSION" +fi + +if gh release view "$VERSION" >/dev/null 2>&1; then + echo "[create_github_release] Updating existing GitHub release $VERSION..." >&2 + gh release upload "$VERSION" "${ASSETS[@]}" --clobber + gh release edit "$VERSION" --title "$TITLE" --notes-file "$NOTES_FILE" +else + echo "[create_github_release] Creating GitHub release $VERSION..." >&2 + gh release create "$VERSION" "${ASSETS[@]}" --title "$TITLE" --notes-file "$NOTES_FILE" --verify-tag +fi diff --git a/scripts/build/extract_release_notes.sh b/scripts/build/extract_release_notes.sh new file mode 100644 index 0000000..6deb85a --- /dev/null +++ b/scripts/build/extract_release_notes.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=scripts/build/release_common.sh +source "$SCRIPT_DIR/release_common.sh" + +usage() { + cat <<'EOF' +Usage: scripts/build/extract_release_notes.sh --version X.Y.Z --output PATH + +Options: + --version VERSION Release version to extract from CHANGELOG.md + --output PATH Output markdown file path + --changelog PATH Override changelog path + --help Show this message +EOF +} + +VERSION="" +OUTPUT_FILE="" +CHANGELOG_PATH="" + +while [ $# -gt 0 ]; do + case "$1" in + --version) + VERSION="${2:-}" + shift 2 + ;; + --output) + OUTPUT_FILE="${2:-}" + shift 2 + ;; + --changelog) + CHANGELOG_PATH="${2:-}" + shift 2 + ;; + --help) + usage + exit 0 + ;; + *) + usage >&2 + release_die "Unknown argument: $1" + ;; + esac +done + +[ -n "$VERSION" ] || release_die "--version is required" +[ -n "$OUTPUT_FILE" ] || release_die "--output is required" +release_validate_version "$VERSION" + +REPO_ROOT="$(release_repo_root)" +release_extract_changelog_section "$REPO_ROOT" "$VERSION" "$OUTPUT_FILE" "${CHANGELOG_PATH:-$REPO_ROOT/CHANGELOG.md}" diff --git a/scripts/build/package_release_artifact.sh b/scripts/build/package_release_artifact.sh new file mode 100644 index 0000000..5eb2cc1 --- /dev/null +++ b/scripts/build/package_release_artifact.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=scripts/build/release_common.sh +source "$SCRIPT_DIR/release_common.sh" + +usage() { + cat <<'EOF' +Usage: scripts/build/package_release_artifact.sh --version X.Y.Z [options] + +Options: + --version VERSION Release version (required) + --git-hash HASH Short git hash to embed in artifact naming + --full-git-hash HASH Full git hash to archive + --output PATH Output zip path + --bundle-name NAME Bundle folder name inside the zip + --skip-prebuilt-build Reuse existing frontend/prebuilt instead of rebuilding it + --help Show this message +EOF +} + +VERSION="" +GIT_HASH="" +FULL_GIT_HASH="" +OUTPUT_PATH="" +BUNDLE_NAME="Remote-Terminal-for-MeshCore" +SKIP_PREBUILT_BUILD=0 + +while [ $# -gt 0 ]; do + case "$1" in + --version) + VERSION="${2:-}" + shift 2 + ;; + --git-hash) + GIT_HASH="${2:-}" + shift 2 + ;; + --full-git-hash) + FULL_GIT_HASH="${2:-}" + shift 2 + ;; + --output) + OUTPUT_PATH="${2:-}" + shift 2 + ;; + --bundle-name) + BUNDLE_NAME="${2:-}" + shift 2 + ;; + --skip-prebuilt-build) + SKIP_PREBUILT_BUILD=1 + shift + ;; + --help) + usage + exit 0 + ;; + *) + usage >&2 + release_die "Unknown argument: $1" + ;; + esac +done + +[ -n "$VERSION" ] || release_die "--version is required" +release_validate_version "$VERSION" + +REPO_ROOT="$(release_repo_root)" +FULL_GIT_HASH="${FULL_GIT_HASH:-$(release_resolve_full_hash "$REPO_ROOT")}" +GIT_HASH="${GIT_HASH:-$(release_resolve_short_hash "$REPO_ROOT" "$FULL_GIT_HASH")}" +OUTPUT_PATH="${OUTPUT_PATH:-$REPO_ROOT/remoteterm-prebuilt-frontend-v${VERSION}-${GIT_HASH}.zip}" + +WORK_DIR="$(mktemp -d)" +BUNDLE_DIR="$WORK_DIR/$BUNDLE_NAME" + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +if [ "$SKIP_PREBUILT_BUILD" -eq 0 ]; then + echo "[package_release_artifact] Building frontend prebuilt bundle..." >&2 + ( + cd "$REPO_ROOT/frontend" + npm run packaged-build + ) +fi + +[ -d "$REPO_ROOT/frontend/prebuilt" ] || release_die "frontend/prebuilt is missing; run with frontend built or omit --skip-prebuilt-build" + +mkdir -p "$BUNDLE_DIR/frontend" +git -C "$REPO_ROOT" archive "$FULL_GIT_HASH" | tar -x -C "$BUNDLE_DIR" +cp -R "$REPO_ROOT/frontend/prebuilt" "$BUNDLE_DIR/frontend/prebuilt" + +cat > "$BUNDLE_DIR/build_info.json" <&2 + release_die "Unknown argument: $1" + ;; + esac +done echo -e "${YELLOW}=== RemoteTerm for MeshCore Publish Script ===${NC}" echo -# Run backend linting and type checking -echo -e "${YELLOW}Running backend lint (Ruff)...${NC}" -uv run ruff check app/ tests/ --fix -uv run ruff format app/ tests/ -# validate -uv run ruff check app/ tests/ -uv run ruff format --check app/ tests/ -echo -e "${GREEN}Backend lint passed!${NC}" -echo - -echo -e "${YELLOW}Running backend type check (Pyright)...${NC}" -uv run pyright app/ -echo -e "${GREEN}Backend type check passed!${NC}" -echo - -# Run backend tests -echo -e "${YELLOW}Running backend tests...${NC}" -PYTHONPATH=. uv run pytest tests/ -v -echo -e "${GREEN}Backend tests passed!${NC}" -echo - -# Run frontend linting and formatting check -echo -e "${YELLOW}Running frontend lint (ESLint)...${NC}" -cd "$REPO_ROOT/frontend" -npm run lint -echo -e "${GREEN}Frontend lint passed!${NC}" -echo - -echo -e "${YELLOW}Checking frontend formatting (Prettier)...${NC}" -npm run format:check -echo -e "${GREEN}Frontend formatting OK!${NC}" -echo - -# Run frontend tests and build -echo -e "${YELLOW}Running frontend tests...${NC}" -npm run test:run -echo -e "${GREEN}Frontend tests passed!${NC}" -echo - -echo -e "${YELLOW}Building frontend...${NC}" -npm run build -echo -e "${GREEN}Frontend build complete!${NC}" -cd "$REPO_ROOT" -echo +if [ "$SKIP_QUALITY" -eq 0 ]; then + echo -e "${YELLOW}Running repo quality gate...${NC}" + ./scripts/quality/all_quality.sh + echo -e "${GREEN}Quality gate passed!${NC}" + echo +fi echo -e "${YELLOW}Regenerating LICENSES.md...${NC}" bash scripts/build/collect_licenses.sh LICENSES.md @@ -91,13 +81,11 @@ echo -n " package.json: " grep '"version"' frontend/package.json | head -1 | sed 's/.*"version": "\(.*\)".*/\1/' echo -read -r -p "Enter new version (e.g., 1.2.3): " VERSION -VERSION="$(printf '%s' "$VERSION" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')" - -if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo -e "${RED}Error: Version must be in format X.Y.Z${NC}" - exit 1 +if [ -z "$VERSION" ]; then + read -r -p "Enter new version (e.g., 1.2.3): " VERSION fi +VERSION="$(release_trim "$VERSION")" +release_validate_version "$VERSION" # Update pyproject.toml echo -e "${YELLOW}Updating pyproject.toml...${NC}" @@ -115,18 +103,28 @@ echo -e "${GREEN}Version updated to $VERSION${NC}" echo # Prompt for changelog entry -echo -e "${YELLOW}Enter changelog entry for version $VERSION${NC}" -echo -e "${YELLOW}(Enter your changes, then press Ctrl+D when done):${NC}" -echo - -CHANGELOG_ENTRY=$(cat) - -format_changelog_entry() { - local entry="$1" - printf '%s\n' "$entry" | sed '/^[[:space:]]*$/d; s/^[[:space:]]*//; s/^/* /' +RAW_CHANGELOG_INPUT_FILE="$(mktemp)" +FORMATTED_CHANGELOG_INPUT_FILE="$(mktemp)" +cleanup() { + rm -f "$RAW_CHANGELOG_INPUT_FILE" "$FORMATTED_CHANGELOG_INPUT_FILE" + rm -rf "${REPO_ROOT:?}/frontend/prebuilt" + if [ -n "$RELEASE_ASSET_PATH" ] && [ -f "$RELEASE_ASSET_PATH" ]; then + rm -f "$RELEASE_ASSET_PATH" + fi } +trap cleanup EXIT -FORMATTED_CHANGELOG_ENTRY=$(format_changelog_entry "$CHANGELOG_ENTRY") +if [ -n "$NOTES_FILE" ]; then + cp "$NOTES_FILE" "$RAW_CHANGELOG_INPUT_FILE" +else + echo -e "${YELLOW}Enter changelog entry for version $VERSION${NC}" + echo -e "${YELLOW}(Enter your changes, then press Ctrl+D when done):${NC}" + echo + cat > "$RAW_CHANGELOG_INPUT_FILE" +fi + +release_format_markdown_list "$RAW_CHANGELOG_INPUT_FILE" "$FORMATTED_CHANGELOG_INPUT_FILE" +[ -s "$FORMATTED_CHANGELOG_INPUT_FILE" ] || release_die "Changelog entry cannot be empty" # Create changelog entry with date DATE=$(date +%Y-%m-%d) @@ -142,7 +140,7 @@ if [ -f CHANGELOG.md ]; then echo echo "$CHANGELOG_HEADER" echo - echo "$FORMATTED_CHANGELOG_ENTRY" + cat "$FORMATTED_CHANGELOG_INPUT_FILE" echo tail -n +2 CHANGELOG.md } > CHANGELOG.md.tmp @@ -152,7 +150,7 @@ if [ -f CHANGELOG.md ]; then { echo "$CHANGELOG_HEADER" echo - echo "$FORMATTED_CHANGELOG_ENTRY" + cat "$FORMATTED_CHANGELOG_INPUT_FILE" echo cat CHANGELOG.md } > CHANGELOG.md.tmp @@ -165,7 +163,7 @@ else echo echo "$CHANGELOG_HEADER" echo - echo "$FORMATTED_CHANGELOG_ENTRY" + cat "$FORMATTED_CHANGELOG_INPUT_FILE" } > CHANGELOG.md fi @@ -185,93 +183,33 @@ echo GIT_HASH=$(git rev-parse --short HEAD) FULL_GIT_HASH=$(git rev-parse HEAD) RELEASE_ASSET="remoteterm-prebuilt-frontend-v${VERSION}-${GIT_HASH}.zip" +RELEASE_ASSET_PATH="$REPO_ROOT/$RELEASE_ASSET" echo -e "${YELLOW}Building packaged frontend artifact...${NC}" -cd "$REPO_ROOT/frontend" -npm run packaged-build -cd "$REPO_ROOT" - -RELEASE_WORK_DIR=$(mktemp -d) -RELEASE_BUNDLE_DIR="$RELEASE_WORK_DIR/$RELEASE_BUNDLE_DIR_NAME" -mkdir -p "$RELEASE_BUNDLE_DIR" -git archive "$FULL_GIT_HASH" | tar -x -C "$RELEASE_BUNDLE_DIR" -mkdir -p "$RELEASE_BUNDLE_DIR/frontend" -cp -R "$REPO_ROOT/frontend/prebuilt" "$RELEASE_BUNDLE_DIR/frontend/prebuilt" -cat > "$RELEASE_BUNDLE_DIR/build_info.json" </dev/null 2>&1; then - echo -e "${RED}Error: docker buildx is required for multi-arch Docker builds.${NC}" - exit 1 -fi - -CURRENT_BUILDER="$(docker buildx inspect --format '{{ .Name }}' 2>/dev/null || true)" -if [ -n "$CURRENT_BUILDER" ]; then - docker buildx inspect --bootstrap >/dev/null -elif docker buildx inspect remoteterm-multiarch >/dev/null 2>&1; then - docker buildx use remoteterm-multiarch >/dev/null - docker buildx inspect --bootstrap >/dev/null -else - docker buildx create --name remoteterm-multiarch --use >/dev/null - docker buildx inspect --bootstrap >/dev/null -fi - -docker buildx build \ - --platform "$DOCKER_PLATFORMS" \ - --build-arg COMMIT_HASH="$GIT_HASH" \ - -t "$DOCKER_IMAGE:latest" \ - -t "$DOCKER_IMAGE:$VERSION" \ - -t "$DOCKER_IMAGE:$GIT_HASH" \ - --push \ - . +scripts/build/push_docker_multiarch.sh \ + --version "$VERSION" \ + --git-hash "$GIT_HASH" \ + --image "$DOCKER_IMAGE" \ + --platforms "$DOCKER_PLATFORMS" echo -e "${GREEN}Multi-arch Docker build + push complete!${NC}" echo # Create GitHub release using the changelog notes for this version. echo -e "${YELLOW}Creating GitHub release...${NC}" -RELEASE_NOTES_FILE=$(mktemp) -{ - echo "$CHANGELOG_HEADER" - echo - echo "$FORMATTED_CHANGELOG_ENTRY" -} > "$RELEASE_NOTES_FILE" - -# Create and push the release tag first so GitHub release creation does not -# depend on resolving a symbolic ref like HEAD on the remote side. Use the same -# changelog-derived notes for the annotated tag message. -if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then - echo -e "${YELLOW}Tag $VERSION already exists locally; reusing it.${NC}" -else - git tag -a "$VERSION" "$FULL_GIT_HASH" -F "$RELEASE_NOTES_FILE" -fi - -if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then - echo -e "${YELLOW}Tag $VERSION already exists on origin; not pushing it again.${NC}" -else - git push origin "$VERSION" -fi - -gh release create "$VERSION" \ - "$RELEASE_ASSET" \ - --title "$VERSION" \ - --notes-file "$RELEASE_NOTES_FILE" \ - --verify-tag - -rm -f "$RELEASE_NOTES_FILE" +scripts/build/create_github_release.sh \ + --version "$VERSION" \ + --full-git-hash "$FULL_GIT_HASH" \ + --asset "$RELEASE_ASSET_PATH" echo -e "${GREEN}GitHub release created!${NC}" echo @@ -285,6 +223,7 @@ echo -e " - $DOCKER_IMAGE:$GIT_HASH" echo -e "Platforms:" echo -e " - linux/amd64" echo -e " - linux/arm64" +echo -e " - linux/arm/v7" echo -e "GitHub release:" echo -e " - $VERSION" echo -e "Release artifact:" diff --git a/scripts/build/push_docker_multiarch.sh b/scripts/build/push_docker_multiarch.sh new file mode 100644 index 0000000..2c7c4ca --- /dev/null +++ b/scripts/build/push_docker_multiarch.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=scripts/build/release_common.sh +source "$SCRIPT_DIR/release_common.sh" + +usage() { + cat <<'EOF' +Usage: scripts/build/push_docker_multiarch.sh --version X.Y.Z [options] + +Options: + --version VERSION Release version (required) + --git-hash HASH Short git hash to tag alongside the version + --image IMAGE Docker image name (default: docker.io/jkingsman/remoteterm-meshcore) + --platforms CSV Buildx platforms CSV (default: linux/amd64,linux/arm64,linux/arm/v7) + --help Show this message +EOF +} + +VERSION="" +GIT_HASH="" +IMAGE="docker.io/jkingsman/remoteterm-meshcore" +PLATFORMS="linux/amd64,linux/arm64,linux/arm/v7" + +while [ $# -gt 0 ]; do + case "$1" in + --version) + VERSION="${2:-}" + shift 2 + ;; + --git-hash) + GIT_HASH="${2:-}" + shift 2 + ;; + --image) + IMAGE="${2:-}" + shift 2 + ;; + --platforms) + PLATFORMS="${2:-}" + shift 2 + ;; + --help) + usage + exit 0 + ;; + *) + usage >&2 + release_die "Unknown argument: $1" + ;; + esac +done + +[ -n "$VERSION" ] || release_die "--version is required" +release_validate_version "$VERSION" + +REPO_ROOT="$(release_repo_root)" +GIT_HASH="${GIT_HASH:-$(release_resolve_short_hash "$REPO_ROOT")}" + +echo "[push_docker_multiarch] Ensuring docker buildx builder..." >&2 +release_ensure_buildx_builder + +docker_buildx_args=( + build + --platform "$PLATFORMS" + --build-arg "COMMIT_HASH=$GIT_HASH" + -t "$IMAGE:latest" + -t "$IMAGE:$VERSION" + -t "$IMAGE:$GIT_HASH" + --push + . +) + +echo "[push_docker_multiarch] Building and pushing $IMAGE for $PLATFORMS..." >&2 +( + cd "$REPO_ROOT" + docker buildx "${docker_buildx_args[@]}" +) diff --git a/scripts/build/release_common.sh b/scripts/build/release_common.sh new file mode 100644 index 0000000..ad8ae28 --- /dev/null +++ b/scripts/build/release_common.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +release_repo_root() { + ( + cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd + ) +} + +release_die() { + echo "Error: $*" >&2 + exit 1 +} + +release_trim() { + printf '%s' "$1" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' +} + +release_validate_version() { + local version="$1" + [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || release_die "Version must be in format X.Y.Z" +} + +release_resolve_full_hash() { + local repo_root="$1" + local ref="${2:-HEAD}" + git -C "$repo_root" rev-parse "$ref" +} + +release_resolve_short_hash() { + local repo_root="$1" + local ref="${2:-HEAD}" + git -C "$repo_root" rev-parse --short "$ref" +} + +release_format_markdown_list() { + local input_file="$1" + local output_file="$2" + awk ' + /^[[:space:]]*$/ { next } + { + sub(/^[[:space:]]+/, "", $0) + if ($0 ~ /^\* /) { + print + } else if ($0 ~ /^- /) { + sub(/^- /, "* ", $0) + print + } else { + print "* " $0 + } + } + ' "$input_file" > "$output_file" +} + +release_extract_changelog_section() { + local repo_root="$1" + local version="$2" + local output_file="$3" + local changelog_path="${4:-$repo_root/CHANGELOG.md}" + + # Use index() for literal matching so dots in version strings are not + # treated as regex wildcards (e.g. 3.6.5 won't match 31615). + awk -v ver="$version" ' + BEGIN { header = "## [" ver "]" } + index($0, header) == 1 { capture = 1; print; next } + capture && /^## \[/ { exit } + capture { print } + ' "$changelog_path" > "$output_file" + + [ -s "$output_file" ] || release_die "Could not find CHANGELOG entry for version $version" +} + +release_ensure_buildx_builder() { + if ! docker buildx version >/dev/null 2>&1; then + release_die "docker buildx is required for multi-arch Docker builds" + fi + + # Multi-platform builds require the docker-container driver. The default + # builder uses the "docker" driver which only supports the host platform. + # Check the current builder's driver first; only create a new one if needed. + local current_driver + current_driver="$(docker buildx inspect --format '{{ .Driver }}' 2>/dev/null || true)" + if [ "$current_driver" = "docker-container" ]; then + docker buildx inspect --bootstrap >/dev/null + return + fi + + if docker buildx inspect remoteterm-multiarch >/dev/null 2>&1; then + docker buildx use remoteterm-multiarch >/dev/null + else + docker buildx create --name remoteterm-multiarch --use >/dev/null + fi + docker buildx inspect --bootstrap >/dev/null +} diff --git a/uv.lock b/uv.lock index 56fb427..317991f 100644 --- a/uv.lock +++ b/uv.lock @@ -1098,7 +1098,7 @@ wheels = [ [[package]] name = "remoteterm-meshcore" -version = "3.6.5" +version = "3.6.6" source = { virtual = "." } dependencies = [ { name = "aiomqtt" },