diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..343d0e0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,110 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + name: Build (${{ matrix.platform }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - platform: Linux + runner: ubuntu-26.04 + asset: grr-linux-x86_64 + extension: '' + - platform: macOS + runner: macos-15 + asset: grr-macos-arm64 + extension: '' + - platform: Windows + runner: windows-2025 + asset: grr-windows-x86_64.exe + extension: .exe + defaults: + run: + shell: bash + env: + CARGO_BUILD_JOBS: 2 + RUST_BACKTRACE: 1 + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + - name: Set up Node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 + with: + node-version: 22.22.2 + - name: Check release version + env: + RELEASE_TAG: ${{ github.ref_name }} + run: node scripts/check-release-version.mjs "$RELEASE_TAG" + - name: Set up Linux development environment + if: runner.os == 'Linux' + uses: ./.github/actions/setup-linux + - name: Build Linux executable + if: runner.os == 'Linux' + run: npm run build:app + - name: Check system libgit2 linkage + if: runner.os == 'Linux' + run: npm run check:linux + - name: Check and build desktop executable + if: runner.os != 'Linux' + uses: ./.github/actions/check-desktop + env: + LIBGIT2_NO_PKG_CONFIG: '1' + - name: Stage executable + env: + RELEASE_ASSET: ${{ matrix.asset }} + EXECUTABLE_EXTENSION: ${{ matrix.extension }} + run: | + mkdir -p artifacts + cp "src-tauri/target/release/grr$EXECUTABLE_EXTENSION" "artifacts/$RELEASE_ASSET" + - name: Upload executable + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: release-${{ matrix.asset }} + path: artifacts/* + if-no-files-found: error + retention-days: 7 + + publish: + name: Publish release + needs: build + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: write + steps: + - name: Download executables + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + pattern: release-* + path: artifacts + merge-multiple: true + - name: Write checksums + working-directory: artifacts + run: sha256sum grr-* > SHA256SUMS + - name: Publish release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + args=(--verify-tag --generate-notes --title "grr $RELEASE_TAG") + if [[ "$RELEASE_TAG" == *-* ]]; then + args+=(--prerelease) + fi + gh release create "$RELEASE_TAG" artifacts/* "${args[@]}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b195ed9..4175929 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,39 @@ # Contributing -Use Rust 1.90+, Node 22.20+ within Node 22, and the checked-in lockfiles. Native development currently targets Fedora Linux 44; see the system dependencies in [README.md](README.md). +Use Rust 1.90+, Node 22.20+ within Node 22, and the checked-in lockfiles. Native development currently targets Fedora Linux 44; see the [installation requirements](#installation-requirements). + +## Installation requirements + +The Linux release binary is built on Ubuntu 26.04 and requires compatible system libraries. On Fedora 44: + +```sh +sudo dnf install webkit2gtk4.1 libgit2 openssl-libs libappindicator-gtk3 librsvg2 libxdo wl-clipboard +``` + +Clipboard copying on Wayland uses `wl-copy` from `wl-clipboard`. Windows needs WebView2, which is included with Windows 10 and 11. macOS native window chrome requires macOS 11 or later. + +## Build from source + +Requires Rust 1.90+, Node.js 22.20+ (Node 22), npm, and system libgit2 1.9.x. + +```sh +sudo dnf install webkit2gtk4.1-devel libgit2-devel openssl-devel \ + libappindicator-gtk3-devel librsvg2-devel libxdo-devel +sudo dnf group install "c-development" + +npm ci +npm run install:local +``` + +Make sure Cargo's bin directory is on your `PATH`. Installation includes the Linux desktop icon. Clipboard copying requires `wl-copy` from `wl-clipboard`. + +On macOS, install the Xcode command line tools (`xcode-select --install`) and libgit2 (`brew install libgit2`), then use the build commands below. On Windows, install the Visual Studio C++ build tools instead of the Fedora packages. libgit2 is built from source and WebView2 ships with Windows 10 and 11: + +```sh +npm ci +npm run build +cargo install --path src-tauri --locked --features custom-protocol +``` ## Development @@ -12,7 +45,7 @@ npm run check The Preact frontend lives in `ui/`. The Rust app, Tauri configuration, capabilities and icons live in `src-tauri/`. Git parsing uses libgit2; review data and window chrome are separate from the UI. -`npm run build:app` uses the Tauri CLI to build the executable. `npm run install:local` also installs the Linux desktop integration. macOS 11+ uses an AppKit toolbar for native window chrome. Packaged releases are not available yet. +`npm run build:app` uses the Tauri CLI to build the executable. `npm run install:local` also installs the Linux desktop integration. macOS 11+ uses an AppKit toolbar for native window chrome. On macOS, Cargo uses a compatible system libgit2 when available and builds its bundled copy otherwise. For a direct production build on any platform, run `npm run build` at the project root, then `cargo build --release --locked` from `src-tauri`. The default `custom-protocol` feature embeds the frontend; rebuild it after frontend changes. `tauri dev` disables this feature to use the development server. @@ -55,4 +88,8 @@ The native test stack overrides WebdriverIO's transitive Mocha, diff parser, Jav CI has independent jobs for frontend checks, Rust tests and coverage, Windows and macOS Rust checks, native Linux tests, dependency audits, secret scanning and workflow validation. It runs on pushes, pull requests or manual dispatch with read-only permissions and no repository secrets. The Rust and native Linux jobs run directly on Ubuntu 26.04 and share the setup action in `.github/actions/setup-linux`. Windows and macOS share `.github/actions/check-desktop` for setup, formatting, Clippy, Rust tests and the release build. The macOS job uses an Apple Silicon runner on macOS 15. -Require `Frontend`, `Rust checks and coverage`, `Rust checks (Windows)`, `Rust checks (macOS)`, `Native tests (Linux)`, `Dependency audit`, `Secret scan` and `Workflow lint` checks for `main` after the first hosted run succeeds. CI does not publish releases. Native macOS window behavior still needs manual validation. +Require `Frontend`, `Rust checks and coverage`, `Rust checks (Windows)`, `Rust checks (macOS)`, `Native tests (Linux)`, `Dependency audit`, `Secret scan` and `Workflow lint` checks for `main` after the first hosted run succeeds. Version tags publish releases through `.github/workflows/release.yml` after Linux, Apple Silicon macOS, and Windows builds succeed. + +## Release + +Update the version in `package.json`, `package-lock.json`, `src-tauri/Cargo.toml`, `src-tauri/Cargo.lock`, and `src-tauri/tauri.conf.json` together. Commit the version change on `main`, then tag that commit with the matching `v` version (for example, `v0.6.2`) and push the tag. The release workflow rejects mismatched versions, builds all three executables, and publishes them with checksums and generated release notes. Tags containing a prerelease suffix publish a GitHub prerelease. diff --git a/README.md b/README.md index 5b02aa5..e7053be 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,39 @@ # grr -A small local Git review app. Browse a unified diff, add line comments, and send the review back to your terminal. +A small local Git review app. Browse diffs and commit history, add line comments, and send the review back to your terminal. -Currently tested on Fedora Linux 44 and Windows 10. Native macOS window chrome is available on macOS 11+, with broader platform validation still in progress. +Tested on Fedora Linux 44, Windows 10, and macOS. ## Install -Requires Rust 1.90+, Node.js 22.20+ (Node 22), npm, and system libgit2 1.9.x. +Download the [latest release](https://github.com/RubyElders/grr/releases/latest) for your platform: -```sh -sudo dnf install webkit2gtk4.1-devel libgit2-devel openssl-devel \ - libappindicator-gtk3-devel librsvg2-devel libxdo-devel -sudo dnf group install "c-development" +### Linux (x86_64) -npm ci -npm run install:local +```sh +mkdir -p ~/.local/bin +curl -fL https://github.com/RubyElders/grr/releases/latest/download/grr-linux-x86_64 -o ~/.local/bin/grr +chmod +x ~/.local/bin/grr ``` -Make sure Cargo's bin directory is on your `PATH`. Installation includes the Linux desktop icon. Clipboard copying requires `wl-copy` from `wl-clipboard`. +Requires WebKitGTK 4.1 and libgit2 1.9.x. See [Linux dependencies](CONTRIBUTING.md#installation-requirements). -On Windows, install the Visual Studio C++ build tools instead of the Fedora packages. libgit2 is built from source and WebView2 ships with Windows 10 and 11: +### macOS (Apple Silicon) ```sh -npm ci -npm run build -cargo install --path src-tauri --locked --features custom-protocol +mkdir -p ~/.local/bin +curl -fL https://github.com/RubyElders/grr/releases/latest/download/grr-macos-arm64 -o ~/.local/bin/grr +chmod +x ~/.local/bin/grr +``` + +Releases are unsigned. If blocked, allow the app in System Settings > Privacy & Security. + +For Linux and macOS, make sure `~/.local/bin` is on your `PATH`. + +### Windows (x86_64) + +```powershell +Invoke-WebRequest https://github.com/RubyElders/grr/releases/latest/download/grr-windows-x86_64.exe -OutFile "$env:LOCALAPPDATA\Microsoft\WindowsApps\grr.exe" ``` ## Use @@ -36,14 +45,8 @@ grr log grr -C ../another-project ``` -`grr` (or `grr diff`) opens the changes and `grr log` opens the commit history; press `v` to switch between them. The app detects the base branch automatically; pass a revision such as `main` (or `--merge-base main`) to choose it. Like Git, `-C` opens another repository. Run `grr help` for all options. Uncommitted changes open as a separate virtual commit; use the commit picker to review individual commits or the whole branch. - -Add comments with the line's `+` button. `Ctrl+Enter` approves or shares comments; `Ctrl+Alt+Enter` also copies the result. Press `?` for all shortcuts. Closing the window cancels the review. - -Reviews stay local and results are printed to stdout. Use `npm run uninstall:local` to remove the app. - -See [CONTRIBUTING.md](CONTRIBUTING.md) for development and tests. +Press `?` for shortcuts. Run `grr help` for options. Reviews stay local and results are printed to stdout. -## License +See [CONTRIBUTING.md](CONTRIBUTING.md) to build from source or contribute. [MIT](LICENSE), including the app icon. Third-party dependencies retain their own licenses. diff --git a/scripts/check-release-version.mjs b/scripts/check-release-version.mjs new file mode 100644 index 0000000..4ac6105 --- /dev/null +++ b/scripts/check-release-version.mjs @@ -0,0 +1,22 @@ +import { readFileSync } from "node:fs"; + +const readJson = (path) => JSON.parse(readFileSync(path, "utf8")); +const packageVersion = readJson("package.json").version; +const lock = readJson("package-lock.json"); +const tauriVersion = readJson("src-tauri/tauri.conf.json").version; +const cargo = readFileSync("src-tauri/Cargo.toml", "utf8"); +const cargoVersion = cargo.match(/^\[package\]\s*\n[\s\S]*?^version\s*=\s*"([^"]+)"/m)?.[1]; +const cargoLock = readFileSync("src-tauri/Cargo.lock", "utf8"); +const cargoLockVersion = cargoLock.match(/^name = "grr"\nversion = "([^"]+)"/m)?.[1]; +const versions = [packageVersion, lock.version, lock.packages[""].version, tauriVersion, cargoVersion, cargoLockVersion]; +const tag = process.argv[2]; + +if ( + !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(packageVersion) + || versions.some((version) => version !== packageVersion) + || tag !== `v${packageVersion}` +) { + throw new Error(`Release tag ${tag} must match all package versions: ${versions.join(", ")}`); +} + +console.log(`Release ${tag} matches all package versions`);