Release #78
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| build_macos: | |
| runs-on: macos-latest | |
| environment: release | |
| env: | |
| CODESIGN_IDENTITY: ${{ vars.CODESIGN_IDENTITY }} | |
| NOTARY_PROFILE_NAME: ${{ vars.NOTARY_PROFILE_NAME }} | |
| APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| TAURI_SIGNING_PRIVATE_KEY_B64: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_B64 }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Rust cache | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: './src-tauri -> target' | |
| - name: Install CMake | |
| run: brew install cmake | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Import signing certificate | |
| env: | |
| APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| KEYCHAIN=build.keychain | |
| CERT_PATH=cert.p12 | |
| echo "$APPLE_CERTIFICATE_P12" | base64 --decode > "$CERT_PATH" 2>/dev/null || \ | |
| echo "$APPLE_CERTIFICATE_P12" | base64 -D > "$CERT_PATH" | |
| security create-keychain -p "" "$KEYCHAIN" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN" | |
| security unlock-keychain -p "" "$KEYCHAIN" | |
| security import "$CERT_PATH" -k "$KEYCHAIN" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign | |
| security list-keychains -d user -s "$KEYCHAIN" | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" "$KEYCHAIN" | |
| - name: Configure notarytool credentials | |
| env: | |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
| APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }} | |
| APPLE_API_PRIVATE_KEY_B64: ${{ secrets.APPLE_API_PRIVATE_KEY_B64 }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p private_keys | |
| echo "$APPLE_API_PRIVATE_KEY_B64" | base64 --decode > private_keys/AuthKey.p8 | |
| xcrun notarytool store-credentials "$NOTARY_PROFILE_NAME" \ | |
| --key-id "$APPLE_API_KEY_ID" \ | |
| --issuer "$APPLE_API_ISSUER_ID" \ | |
| --key "private_keys/AuthKey.p8" | |
| - name: Write Tauri signing key | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$HOME/.tauri" | |
| echo "$TAURI_SIGNING_PRIVATE_KEY_B64" | base64 --decode > "$HOME/.tauri/codexmonitor.key" | |
| - name: Build app bundle | |
| run: | | |
| set -euo pipefail | |
| export TAURI_SIGNING_PRIVATE_KEY | |
| TAURI_SIGNING_PRIVATE_KEY="$(cat "$HOME/.tauri/codexmonitor.key")" | |
| npm run tauri -- build --bundles app | |
| - name: Bundle OpenSSL and re-sign | |
| run: | | |
| set -euo pipefail | |
| CODESIGN_IDENTITY="$CODESIGN_IDENTITY" \ | |
| scripts/macos-fix-openssl.sh | |
| - name: Notarize and staple | |
| run: | | |
| set -euo pipefail | |
| ditto -c -k --keepParent \ | |
| "src-tauri/target/release/bundle/macos/Codex Monitor.app" \ | |
| CodexMonitor.zip | |
| xcrun notarytool submit CodexMonitor.zip \ | |
| --keychain-profile "$NOTARY_PROFILE_NAME" \ | |
| --wait | |
| xcrun stapler staple \ | |
| "src-tauri/target/release/bundle/macos/Codex Monitor.app" | |
| - name: Package artifacts | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 - <<'PY' | |
| import json | |
| from pathlib import Path | |
| data = json.loads(Path("src-tauri/tauri.conf.json").read_text()) | |
| print(data["version"]) | |
| PY | |
| ) | |
| mkdir -p release-artifacts release-artifacts/dmg-root | |
| rm -rf "release-artifacts/dmg-root/Codex Monitor.app" | |
| ditto "src-tauri/target/release/bundle/macos/Codex Monitor.app" \ | |
| "release-artifacts/dmg-root/Codex Monitor.app" | |
| ditto -c -k --keepParent \ | |
| "src-tauri/target/release/bundle/macos/Codex Monitor.app" \ | |
| release-artifacts/CodexMonitor.zip | |
| hdiutil create -volname "Codex Monitor" \ | |
| -srcfolder release-artifacts/dmg-root \ | |
| -ov -format UDZO \ | |
| release-artifacts/CodexMonitor_${VERSION}_aarch64.dmg | |
| COPYFILE_DISABLE=1 tar -czf \ | |
| "src-tauri/target/release/bundle/macos/Codex Monitor.app.tar.gz" \ | |
| -C src-tauri/target/release/bundle/macos "Codex Monitor.app" | |
| npm run tauri signer sign -- \ | |
| -f "$HOME/.tauri/codexmonitor.key" \ | |
| -p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \ | |
| "src-tauri/target/release/bundle/macos/Codex Monitor.app.tar.gz" | |
| cp "src-tauri/target/release/bundle/macos/Codex Monitor.app.tar.gz" \ | |
| release-artifacts/CodexMonitor.app.tar.gz | |
| cp "src-tauri/target/release/bundle/macos/Codex Monitor.app.tar.gz.sig" \ | |
| release-artifacts/CodexMonitor.app.tar.gz.sig | |
| - name: Upload macOS artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-artifacts | |
| path: | | |
| release-artifacts/CodexMonitor.zip | |
| release-artifacts/CodexMonitor_*_aarch64.dmg | |
| release-artifacts/CodexMonitor.app.tar.gz | |
| release-artifacts/CodexMonitor.app.tar.gz.sig | |
| build_linux: | |
| name: linux bundles (${{ matrix.arch }}) | |
| runs-on: ${{ matrix.platform }} | |
| environment: release | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| TAURI_SIGNING_PRIVATE_KEY_B64: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_B64 }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: ubuntu-24.04 | |
| arch: x86_64 | |
| - platform: ubuntu-24.04-arm | |
| arch: aarch64 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: install dependencies (linux only) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y cmake libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf libfuse2 xdg-utils libasound2-dev rpm | |
| - name: setup node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: lts/* | |
| cache: npm | |
| - name: install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Rust cache | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: './src-tauri -> target' | |
| - name: install frontend dependencies | |
| run: npm ci | |
| - name: Write Tauri signing key | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$HOME/.tauri" | |
| echo "$TAURI_SIGNING_PRIVATE_KEY_B64" | base64 --decode > "$HOME/.tauri/codexmonitor.key" | |
| - name: build AppImage and RPM | |
| run: | | |
| set -euo pipefail | |
| export TAURI_SIGNING_PRIVATE_KEY | |
| TAURI_SIGNING_PRIVATE_KEY="$(cat "$HOME/.tauri/codexmonitor.key")" | |
| npm run tauri -- build --bundles appimage,rpm | |
| - name: Validate Linux bundle outputs | |
| run: | | |
| set -euo pipefail | |
| mapfile -t appimages < <(find src-tauri/target/release/bundle/appimage -type f -name '*.AppImage*' | sort) | |
| mapfile -t rpms < <(find src-tauri/target/release/bundle/rpm -type f -name '*.rpm' | sort) | |
| if [ ${#appimages[@]} -eq 0 ]; then | |
| echo "No AppImage output found" | |
| exit 1 | |
| fi | |
| if [ ${#rpms[@]} -eq 0 ]; then | |
| echo "No RPM output found" | |
| exit 1 | |
| fi | |
| - name: Upload Linux bundles | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-bundles-${{ matrix.arch }} | |
| path: | | |
| src-tauri/target/release/bundle/appimage/*.AppImage* | |
| src-tauri/target/release/bundle/rpm/*.rpm | |
| build_windows: | |
| runs-on: windows-latest | |
| environment: release | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| TAURI_SIGNING_PRIVATE_KEY_B64: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_B64 }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: setup node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: lts/* | |
| cache: npm | |
| - name: install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Rust cache | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: './src-tauri -> target' | |
| - name: Install LLVM (bindgen) | |
| run: choco install llvm -y --no-progress | |
| - name: Configure LLVM (bindgen) | |
| run: | | |
| echo "LIBCLANG_PATH=C:\\Program Files\\LLVM\\bin" >> $env:GITHUB_ENV | |
| echo "C:\\Program Files\\LLVM\\bin" >> $env:GITHUB_PATH | |
| - name: install frontend dependencies | |
| run: npm ci | |
| - name: Write Tauri signing key | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python - <<'PY' | |
| import base64 | |
| import os | |
| from pathlib import Path | |
| raw = base64.b64decode(os.environ["TAURI_SIGNING_PRIVATE_KEY_B64"]) | |
| home = Path.home() | |
| target = home / ".tauri" | |
| target.mkdir(parents=True, exist_ok=True) | |
| (target / "codexmonitor.key").write_bytes(raw) | |
| PY | |
| - name: build windows bundles | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| export TAURI_SIGNING_PRIVATE_KEY | |
| TAURI_SIGNING_PRIVATE_KEY="$(cat "$HOME/.tauri/codexmonitor.key")" | |
| npm run tauri:build:win | |
| - name: Upload Windows artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-artifacts | |
| path: | | |
| src-tauri/target/release/bundle/nsis/*.exe* | |
| src-tauri/target/release/bundle/msi/*.msi* | |
| release: | |
| runs-on: ubuntu-latest | |
| environment: release | |
| needs: | |
| - build_macos | |
| - build_linux | |
| - build_windows | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download macOS artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: macos-artifacts | |
| path: release-artifacts | |
| - name: Download Linux bundles | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: linux-bundles-* | |
| path: release-artifacts | |
| merge-multiple: true | |
| - name: Download Windows artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: windows-artifacts | |
| path: release-artifacts | |
| - name: Normalize release artifact filenames | |
| run: | | |
| set -euo pipefail | |
| while IFS= read -r -d '' artifact; do | |
| dir=$(dirname "$artifact") | |
| file=$(basename "$artifact") | |
| normalized="${file// /.}" | |
| if [ "$file" = "$normalized" ]; then | |
| continue | |
| fi | |
| target="${dir}/${normalized}" | |
| if [ -e "$target" ]; then | |
| echo "Cannot normalize filename because target already exists: $target" | |
| exit 1 | |
| fi | |
| echo "Renaming $artifact -> $target" | |
| mv "$artifact" "$target" | |
| done < <(find release-artifacts -type f -name '* *' -print0) | |
| - name: Validate RPM artifacts | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y rpm | |
| mapfile -t rpms < <(find release-artifacts -type f -name '*.rpm' | sort) | |
| if [ ${#rpms[@]} -eq 0 ]; then | |
| echo "No RPM artifacts found" | |
| find release-artifacts -type f | sort | |
| exit 1 | |
| fi | |
| for rpm_file in "${rpms[@]}"; do | |
| rpm -qip "$rpm_file" | |
| rpm -qp --requires "$rpm_file" | |
| done | |
| - name: Build latest.json | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 - <<'PY' | |
| import json | |
| from pathlib import Path | |
| data = json.loads(Path("src-tauri/tauri.conf.json").read_text()) | |
| print(data["version"]) | |
| PY | |
| ) | |
| SIGNATURE=$(cat release-artifacts/CodexMonitor.app.tar.gz.sig) | |
| LAST_TAG=$(git tag --sort=-version:refname \ | |
| | grep -v "^v${VERSION}$" \ | |
| | head -n 1 || true) | |
| RANGE_END="${GITHUB_SHA}" | |
| if [ -n "$LAST_TAG" ]; then | |
| git log "${LAST_TAG}..${RANGE_END}" --pretty=format:"%s" > release-artifacts/release-commits.txt | |
| else | |
| git log "${RANGE_END}" --pretty=format:"%s" > release-artifacts/release-commits.txt | |
| fi | |
| python3 - <<'PY' | |
| import re | |
| from pathlib import Path | |
| lines = Path("release-artifacts/release-commits.txt").read_text().splitlines() | |
| pattern = re.compile(r"^(feat|fix|perf)(?:\([^)]*\))?:\s*(.+)$", re.IGNORECASE) | |
| groups = {"feat": [], "fix": [], "perf": []} | |
| for line in lines: | |
| match = pattern.match(line.strip()) | |
| if not match: | |
| continue | |
| kind = match.group(1).lower() | |
| message = match.group(2).strip() | |
| if message: | |
| groups[kind].append(message) | |
| sections = [ | |
| ("## New Features", "feat"), | |
| ("## Fixes", "fix"), | |
| ("## Performance Improvements", "perf"), | |
| ] | |
| output = [] | |
| for title, key in sections: | |
| items = groups[key] | |
| if not items: | |
| continue | |
| output.append(title) | |
| output.extend([f"- {item}" for item in items]) | |
| output.append("") | |
| notes = "\n".join(output).strip() | |
| if not notes: | |
| notes = "- No user-facing changes." | |
| Path("release-artifacts/release-notes.md").write_text(notes + "\n") | |
| PY | |
| python3 - <<PY | |
| import json | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| from urllib.parse import quote | |
| notes = Path("release-artifacts/release-notes.md").read_text().strip() | |
| artifacts_dir = Path("release-artifacts") | |
| def release_url(filename): | |
| return f"https://github.com/Dimillian/CodexMonitor/releases/download/v${VERSION}/{quote(filename)}" | |
| platforms = { | |
| "darwin-aarch64": { | |
| "url": release_url("CodexMonitor.app.tar.gz"), | |
| "signature": "${SIGNATURE}", | |
| } | |
| } | |
| appimages = list(artifacts_dir.rglob("*.AppImage.tar.gz")) | |
| if not appimages: | |
| appimages = list(artifacts_dir.rglob("*.AppImage")) | |
| if not appimages: | |
| raise SystemExit("No AppImage artifacts found for latest.json") | |
| def detect_arch(name): | |
| lowered = name.lower() | |
| if "aarch64" in lowered or "arm64" in lowered: | |
| return "aarch64" | |
| if "x86_64" in lowered or "amd64" in lowered: | |
| return "x86_64" | |
| return None | |
| for appimage in appimages: | |
| arch = detect_arch(appimage.name) | |
| if not arch: | |
| continue | |
| sig_path = appimage.with_suffix(appimage.suffix + ".sig") | |
| if not sig_path.exists(): | |
| raise SystemExit(f"Missing signature for {appimage.name}") | |
| platforms[f"linux-{arch}"] = { | |
| "url": release_url(appimage.name), | |
| "signature": sig_path.read_text().strip(), | |
| } | |
| exe_candidates = sorted(artifacts_dir.rglob("*.exe"), key=lambda p: p.name.lower()) | |
| windows_installer = None | |
| preferred_installers = [] | |
| for candidate in exe_candidates: | |
| lowered = candidate.name.lower() | |
| if "codexmonitor" in lowered and ("setup" in lowered or "installer" in lowered): | |
| preferred_installers.append(candidate) | |
| if preferred_installers: | |
| windows_installer = preferred_installers[0] | |
| else: | |
| for candidate in exe_candidates: | |
| lowered = candidate.name.lower() | |
| if "setup" in lowered or "installer" in lowered: | |
| windows_installer = candidate | |
| break | |
| if windows_installer is None and exe_candidates: | |
| windows_installer = exe_candidates[0] | |
| if windows_installer is None: | |
| raise SystemExit("No Windows installer (.exe) found for latest.json") | |
| print(f"Selected Windows installer for latest.json: {windows_installer.name}") | |
| win_sig_path = windows_installer.with_suffix(windows_installer.suffix + ".sig") | |
| if not win_sig_path.exists(): | |
| raise SystemExit(f"Missing signature for {windows_installer.name}") | |
| platforms["windows-x86_64"] = { | |
| "url": release_url(windows_installer.name), | |
| "signature": win_sig_path.read_text().strip(), | |
| } | |
| payload = { | |
| "version": "${VERSION}", | |
| "notes": notes, | |
| "pub_date": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "platforms": platforms, | |
| } | |
| Path("release-artifacts/latest.json").write_text(json.dumps(payload, indent=2) + "\n") | |
| PY | |
| - name: Validate latest.json artifact references | |
| run: | | |
| set -euo pipefail | |
| python3 - <<'PY' | |
| import json | |
| from pathlib import Path | |
| from urllib.parse import unquote, urlparse | |
| artifacts_dir = Path("release-artifacts") | |
| latest_path = artifacts_dir / "latest.json" | |
| payload = json.loads(latest_path.read_text()) | |
| available_files = {path.name for path in artifacts_dir.rglob("*") if path.is_file()} | |
| missing = [] | |
| for platform, metadata in payload.get("platforms", {}).items(): | |
| url = metadata.get("url", "") | |
| filename = Path(unquote(urlparse(url).path)).name | |
| if filename not in available_files: | |
| missing.append((platform, filename)) | |
| if missing: | |
| print("latest.json references missing artifacts:") | |
| for platform, filename in missing: | |
| print(f"- {platform}: {filename}") | |
| raise SystemExit(1) | |
| print(f"Validated {len(payload.get('platforms', {}))} latest.json platform URL(s).") | |
| PY | |
| - name: Create GitHub release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 - <<'PY' | |
| import json | |
| from pathlib import Path | |
| data = json.loads(Path("src-tauri/tauri.conf.json").read_text()) | |
| print(data["version"]) | |
| PY | |
| ) | |
| shopt -s nullglob globstar | |
| appimages=(release-artifacts/**/*.AppImage*) | |
| mapfile -t rpms < <(find release-artifacts -type f -name '*.rpm' | sort) | |
| mapfile -t windows_exes < <(find release-artifacts -type f -name '*.exe*' | sort) | |
| mapfile -t windows_msis < <(find release-artifacts -type f -name '*.msi*' | sort) | |
| if [ ${#rpms[@]} -eq 0 ]; then | |
| echo "No RPM artifacts found for release upload" | |
| find release-artifacts -type f | sort | |
| exit 1 | |
| fi | |
| if [ ${#windows_exes[@]} -eq 0 ]; then | |
| echo "No Windows .exe artifacts found for release upload" | |
| find release-artifacts -type f | sort | |
| exit 1 | |
| fi | |
| if [ ${#windows_msis[@]} -eq 0 ]; then | |
| echo "No Windows .msi artifacts found for release upload" | |
| find release-artifacts -type f | sort | |
| exit 1 | |
| fi | |
| gh release create "v${VERSION}" \ | |
| --title "v${VERSION}" \ | |
| --notes-file release-artifacts/release-notes.md \ | |
| --target "$GITHUB_SHA" \ | |
| release-artifacts/CodexMonitor.zip \ | |
| release-artifacts/CodexMonitor_*_aarch64.dmg \ | |
| release-artifacts/CodexMonitor.app.tar.gz \ | |
| release-artifacts/CodexMonitor.app.tar.gz.sig \ | |
| "${appimages[@]}" \ | |
| "${rpms[@]}" \ | |
| "${windows_exes[@]}" \ | |
| "${windows_msis[@]}" \ | |
| release-artifacts/latest.json | |
| - name: Bump version and open PR | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 - <<'PY' | |
| import json | |
| from pathlib import Path | |
| data = json.loads(Path("src-tauri/tauri.conf.json").read_text()) | |
| print(data["version"]) | |
| PY | |
| ) | |
| NEXT_VERSION=$(python3 - <<'PY' "$VERSION" | |
| import sys | |
| version = sys.argv[1] | |
| parts = version.split(".") | |
| if len(parts) != 3: | |
| raise SystemExit("Expected version like 0.X.Y") | |
| major, minor, patch = (int(p) for p in parts) | |
| print(f"{major}.{minor}.{patch + 1}") | |
| PY | |
| ) | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| npm version "$NEXT_VERSION" --no-git-tag-version | |
| python3 - <<PY | |
| import json | |
| from pathlib import Path | |
| path = Path("src-tauri/tauri.conf.json") | |
| data = json.loads(path.read_text()) | |
| data["version"] = "$NEXT_VERSION" | |
| path.write_text(json.dumps(data, indent=2) + "\n") | |
| PY | |
| python3 - <<PY | |
| import re | |
| from pathlib import Path | |
| path = Path("src-tauri/Cargo.toml") | |
| content = path.read_text() | |
| content, count = re.subn( | |
| r'(?m)^version = ".*"$', | |
| 'version = "$NEXT_VERSION"', | |
| content, | |
| count=1, | |
| ) | |
| if count != 1: | |
| raise SystemExit("Failed to update src-tauri/Cargo.toml version") | |
| path.write_text(content) | |
| PY | |
| python3 - <<PY | |
| import re | |
| from pathlib import Path | |
| path = Path("src-tauri/Cargo.lock") | |
| content = path.read_text() | |
| content, count = re.subn( | |
| r'(?ms)(\[\[package\]\]\nname = "codex-monitor"\nversion = ")[^"]+(")', | |
| r'\g<1>$NEXT_VERSION\2', | |
| content, | |
| count=1, | |
| ) | |
| if count != 1: | |
| raise SystemExit("Failed to update codex-monitor version in src-tauri/Cargo.lock") | |
| path.write_text(content) | |
| PY | |
| python3 - <<PY | |
| import plistlib | |
| from pathlib import Path | |
| path = Path("src-tauri/gen/apple/codex-monitor_iOS/Info.plist") | |
| with path.open("rb") as f: | |
| data = plistlib.load(f) | |
| data["CFBundleShortVersionString"] = "$NEXT_VERSION" | |
| data["CFBundleVersion"] = "$NEXT_VERSION" | |
| with path.open("wb") as f: | |
| plistlib.dump(data, f, sort_keys=False) | |
| PY | |
| git checkout -b "chore/bump-version-${NEXT_VERSION}" | |
| git add package.json package-lock.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.conf.json src-tauri/gen/apple/codex-monitor_iOS/Info.plist | |
| git commit -m "chore: bump version to ${NEXT_VERSION}" | |
| git push origin "chore/bump-version-${NEXT_VERSION}" | |
| gh pr create \ | |
| --title "chore: bump version to ${NEXT_VERSION}" \ | |
| --body "Post-release version bump to ${NEXT_VERSION}." \ | |
| --base main \ | |
| --head "chore/bump-version-${NEXT_VERSION}" |