diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0f5b165 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,141 @@ +name: Release + +# Builds installable artifacts (cargo-packager) for macOS / Linux / Windows and attaches them to a +# GitHub Release. Runs on a version tag `v*`; `workflow_dispatch` builds the artifacts without +# releasing (for dry-runs). Separate from ci.yml (which only tests). +on: + push: + tags: ["v*"] + workflow_dispatch: + +concurrency: + group: release-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: package · ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # macos-14 = Apple Silicon (we lipo a universal binary on it); ubuntu-22.04 has libfuse2 for + # AppImage; windows-latest builds the .msi (cargo-packager fetches WiX itself). + os: [macos-14, ubuntu-22.04, windows-latest] + steps: + - uses: actions/checkout@v4 + + - name: Show toolchain + run: rustup show + + # gpui links X11/Wayland/Vulkan/fontconfig (build) + libfuse2 (AppImage tooling needs FUSE2, + # absent on ubuntu-24.04 — hence pinning 22.04). + - name: Install Linux deps + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential pkg-config clang libfontconfig-dev \ + libwayland-dev libxkbcommon-x11-dev libx11-xcb-dev libvulkan1 \ + libfuse2 + + - uses: Swatinem/rust-cache@v2 + + - uses: cargo-bins/cargo-binstall@main + - name: Install cargo-packager + shell: bash + run: | + # binstall is fast but sometimes exits 0 without installing; verify the binary is really + # present and fall back to a source build, then assert it's runnable. + cargo binstall --no-confirm cargo-packager || true + command -v cargo-packager >/dev/null || cargo install cargo-packager --locked + cargo packager --version + + # No before-packaging-command in the manifest, so build the release binary explicitly. + # (macOS builds its own universal binary via lipo in the step below.) + - name: Build release binary (Linux/Windows) + if: runner.os != 'macOS' + run: cargo build --release -p posel-app + + # macOS: build both arches, lipo into target/release, then package. No build hook in the + # manifest, so `--release` simply bundles the universal binary already sitting there. + # (Do NOT pass `--config` — it REPLACES the manifest config, dropping product-name/binaries.) + - name: Package (macOS — universal .app + .dmg) + if: runner.os == 'macOS' + run: | + rustup target add aarch64-apple-darwin x86_64-apple-darwin + cargo build --release --target aarch64-apple-darwin -p posel-app + cargo build --release --target x86_64-apple-darwin -p posel-app + mkdir -p target/release + lipo -create -output target/release/posel \ + target/aarch64-apple-darwin/release/posel \ + target/x86_64-apple-darwin/release/posel + lipo -info target/release/posel + cargo packager --release --formats app,dmg --manifest-path crates/app/Cargo.toml --out-dir "${{ github.workspace }}/dist" + # Guard: the binary inside the bundle must be universal. + lipo -info "$(find dist -path '*Posel.app/Contents/MacOS/posel')" + # Signing is intentionally OFF (unsigned). To enable later, set these from secrets and add a + # conditional `--config` injecting macos.signing-identity — no other change needed: + # env: + # APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + # APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + # APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + # APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + # APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + # APPLE_API_KEY_PATH: ${{ secrets.APPLE_API_KEY_PATH }} + + - name: Package (Linux — AppImage + .deb) + if: runner.os == 'Linux' + run: | + cargo packager --release --formats appimage,deb \ + --manifest-path crates/app/Cargo.toml --out-dir "${{ github.workspace }}/dist" + + - name: Package (Windows — .msi) + if: runner.os == 'Windows' + run: | + cargo packager --release --formats wix ` + --manifest-path crates/app/Cargo.toml --out-dir "${{ github.workspace }}/dist" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: posel-${{ matrix.os }} + # Bundle binaries/installers only — skip the loose unbundled exe/.app dirs. + path: | + dist/*.dmg + dist/*.AppImage + dist/*.deb + dist/*.msi + if-no-files-found: error + + release: + name: publish GitHub Release + needs: build + runs-on: ubuntu-latest + # Only publish on a real version tag; workflow_dispatch stops after `build` (artifacts only). + if: startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/checkout@v4 + + # Fail loudly if the tag and the crate version disagree (tag v0.1.0 ⇔ version 0.1.0). + - name: Check tag matches crate version + run: | + TAG="${GITHUB_REF_NAME#v}" + VER=$(cargo metadata --no-deps --format-version 1 \ + | jq -r '.packages[] | select(.name=="posel-app") | .version') + echo "tag=$TAG crate=$VER" + [ "$TAG" = "$VER" ] || { echo "::error::tag $TAG != crate version $VER"; exit 1; } + + - uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Publish release + uses: softprops/action-gh-release@v2 + with: + files: artifacts/**/* + fail_on_unmatched_files: true + generate_release_notes: true diff --git a/.gitignore b/.gitignore index a8d165b..8dd454e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target +/dist **/*.rs.bk *.pdb .DS_Store diff --git a/README.md b/README.md index cc4dab7..2398dfe 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ is the opposite corner of the design space: ## Status -Working, in active development. v1 targets **macOS**. +Working, in active development. Runs on **macOS, Linux, and Windows**. - **REST** — request editor (method, query/headers/body), streaming response panel, environments + variables, secrets in the OS keychain, request history. @@ -36,6 +36,21 @@ Working, in active development. v1 targets **macOS**. See [docs/PLAN.md](./docs/PLAN.md) for the phased roadmap and what's next. +## Install + +Grab the latest build for your platform from the +[Releases](https://github.com/starvy/posel/releases) page: + +- **macOS** — `.dmg` (universal: Apple Silicon + Intel) +- **Linux** — `.AppImage` (portable) or `.deb` (Debian/Ubuntu) +- **Windows** — `.msi` installer + +> **Unsigned builds.** Releases aren't code-signed yet, so the OS warns on first launch: +> - **macOS** — right-click the app → **Open**, or `xattr -dr com.apple.quarantine /Applications/Posel.app`. +> - **Windows** — SmartScreen → **More info** → **Run anyway**. +> +> This goes away once signing/notarization is set up. + ## Architecture The domain logic is headless and unit-tested; GPUI is confined to the edge (`ui`/`app` only). @@ -58,8 +73,9 @@ stays protocol-agnostic. See [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md). ## Build & run Requires a current stable Rust toolchain (GPUI uses recently-stabilized std features; run -`rustup update stable` if a build fails with `E0658`). macOS only for now — secret storage uses -the macOS keychain. +`rustup update stable` if a build fails with `E0658`). Builds on macOS, Linux, and Windows; +secrets use the platform keychain (macOS Keychain, Windows Credential Manager, Linux +Secret Service). ```bash cargo run -p posel-app # the desktop app (binary: posel) diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 9f266dc..b83a2b3 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -19,3 +19,22 @@ gpui-component.workspace = true gpui-component-assets.workspace = true anyhow.workspace = true tracing-subscriber.workspace = true + +# Installer/bundle config for `cargo packager` (see .github/workflows/release.yml). `version` is read +# from [workspace.package]. GUI assets are embedded in the binary (with_assets), so no `resources`. +# `icons` is a single placeholder PNG — cargo-packager derives the per-platform .icns/.ico from it. +# No `before-packaging-command`: the release binary is built explicitly by the workflow first (the +# macOS leg lipos a universal binary), then `cargo packager --release` bundles target/release/posel. +[package.metadata.packager] +product-name = "Posel" +identifier = "com.starvy.posel" +category = "DeveloperTool" +icons = [ + "../../packaging/icons/icon.icns", + "../../packaging/icons/icon.ico", + "../../packaging/icons/icon.png", +] + +[package.metadata.packager.macos] +minimum-system-version = "11.0" +# signing-identity intentionally unset → unsigned (no failure); CI injects it once certs exist. diff --git a/packaging/icons/icon.icns b/packaging/icons/icon.icns new file mode 100644 index 0000000..290513a Binary files /dev/null and b/packaging/icons/icon.icns differ diff --git a/packaging/icons/icon.ico b/packaging/icons/icon.ico new file mode 100644 index 0000000..a91fa40 Binary files /dev/null and b/packaging/icons/icon.ico differ diff --git a/packaging/icons/icon.png b/packaging/icons/icon.png new file mode 100644 index 0000000..5323b5d Binary files /dev/null and b/packaging/icons/icon.png differ