diff --git a/.github/workflows/homebrew-cask-codexel.yml b/.github/workflows/homebrew-cask-codexel.yml new file mode 100644 index 00000000000..eb26bfc2456 --- /dev/null +++ b/.github/workflows/homebrew-cask-codexel.yml @@ -0,0 +1,177 @@ +name: homebrew-cask-codexel + +on: + workflow_dispatch: + inputs: + tag: + description: "Existing Codexel tag (e.g. codexel-v0.1.3)" + required: true + type: string + +jobs: + build-macos: + name: Build macOS assets - ${{ matrix.target }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 45 + defaults: + run: + working-directory: codex-rs + strategy: + fail-fast: false + matrix: + include: + - runner: macos-14 + target: aarch64-apple-darwin + - runner: macos-13 + target: x86_64-apple-darwin + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} + + - uses: dtolnay/rust-toolchain@1.90 + with: + targets: ${{ matrix.target }} + + - uses: actions/cache@v5 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + ${{ github.workspace }}/codex-rs/target/ + key: cargo-${{ matrix.runner }}-${{ matrix.target }}-release-${{ hashFiles('**/Cargo.lock') }} + + - name: Cargo build + shell: bash + run: cargo build --target ${{ matrix.target }} --release --bin codexel + + - name: Package tarball + shell: bash + run: | + set -euo pipefail + out_dir="${GITHUB_WORKSPACE}/dist" + mkdir -p "$out_dir" + bin_path="target/${{ matrix.target }}/release/codexel" + chmod +x "$bin_path" + tar -C "$(dirname "$bin_path")" -czf "$out_dir/codexel-${{ matrix.target }}.tar.gz" codexel + + - uses: actions/upload-artifact@v6 + with: + name: codexel-${{ matrix.target }}-tarball + path: dist/codexel-${{ matrix.target }}.tar.gz + if-no-files-found: error + + publish-homebrew: + name: Upload assets + update tap cask + needs: build-macos + runs-on: ubuntu-latest + permissions: + contents: write + env: + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + steps: + - name: Validate tag + shell: bash + run: | + set -euo pipefail + [[ "${{ inputs.tag }}" =~ ^codexel-v[0-9]+\.[0-9]+\.[0-9]+(-((alpha|beta)\.[0-9]+))?$ ]] \ + || { echo "Tag '${{ inputs.tag }}' doesn't match expected format"; exit 1; } + + - name: Download tarballs + uses: actions/download-artifact@v7 + with: + path: dist + + - name: Move tarballs into dist/ + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + for tarball in dist/*/codexel-*-apple-darwin.tar.gz; do + mv "$tarball" dist/ + done + ls -la dist + + - name: Upload macOS assets to GitHub Release + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + gh release upload "${{ inputs.tag }}" dist/codexel-*-apple-darwin.tar.gz \ + --repo Ixe1/codexel \ + --clobber + + - name: Compute sha256 + id: shas + shell: bash + run: | + set -euo pipefail + sha_arm=$(sha256sum dist/codexel-aarch64-apple-darwin.tar.gz | cut -d' ' -f1) + sha_intel=$(sha256sum dist/codexel-x86_64-apple-darwin.tar.gz | cut -d' ' -f1) + echo "arm=$sha_arm" >> "$GITHUB_OUTPUT" + echo "intel=$sha_intel" >> "$GITHUB_OUTPUT" + + - name: Verify Homebrew tap token configured + shell: bash + run: | + set -euo pipefail + if [[ -z "${TAP_TOKEN}" ]]; then + echo "Missing HOMEBREW_TAP_GITHUB_TOKEN secret; cannot update Ixe1/homebrew-tap." >&2 + exit 1 + fi + + - name: Checkout Homebrew tap + if: ${{ env.TAP_TOKEN != '' }} + uses: actions/checkout@v6 + with: + repository: Ixe1/homebrew-tap + token: ${{ env.TAP_TOKEN }} + path: homebrew-tap + + - name: Update cask + if: ${{ env.TAP_TOKEN != '' }} + shell: bash + run: | + set -euo pipefail + version="${{ inputs.tag }}" + version="${version#codexel-v}" + sha_arm="${{ steps.shas.outputs.arm }}" + sha_intel="${{ steps.shas.outputs.intel }}" + + mkdir -p homebrew-tap/Casks + cat > homebrew-tap/Casks/codexel.rb <&2 exit 1 @@ -302,7 +331,8 @@ jobs: shell: bash run: | set -euo pipefail - version="${GITHUB_REF_NAME#codexel-v}" + version="${{ inputs.tag }}" + version="${version#codexel-v}" prerelease="false" if [[ "${version}" == *-* ]]; then prerelease="true" @@ -314,18 +344,63 @@ jobs: uses: softprops/action-gh-release@v2 with: name: ${{ steps.release_meta.outputs.version }} - tag_name: ${{ github.ref_name }} + tag_name: ${{ inputs.tag }} files: dist/** prerelease: ${{ steps.release_meta.outputs.prerelease }} generate_release_notes: true + - name: Checkout Homebrew tap + if: ${{ inputs.update_homebrew && env.TAP_TOKEN != '' }} + uses: actions/checkout@v6 + with: + repository: ${{ env.HOMEBREW_TAP_REPO }} + token: ${{ env.TAP_TOKEN }} + path: homebrew-tap + + - name: Update Homebrew cask + if: ${{ inputs.update_homebrew && env.TAP_TOKEN != '' }} + shell: bash + run: | + set -euo pipefail + version="${{ steps.release_meta.outputs.version }}" + sha_arm=$(sha256sum "dist/codexel-aarch64-apple-darwin.tar.gz" | cut -d' ' -f1) + sha_intel=$(sha256sum "dist/codexel-x86_64-apple-darwin.tar.gz" | cut -d' ' -f1) + + mkdir -p homebrew-tap/Casks + cat > homebrew-tap/Casks/codexel.rb < npm i -g @ixe1/codexel
- brew install --cask codexel
+ brew install --cask Ixe1/tap/codexel
or download from GitHub Releases

@@ -48,7 +48,7 @@ npm install -g @ixe1/codexel Alternatively, if you use Homebrew: ```shell -brew install --cask codexel +brew install --cask Ixe1/tap/codexel ``` Then run `codexel`: @@ -71,7 +71,7 @@ Each GitHub Release contains many executables, but in practice, you likely want - x86_64: `codexel-x86_64-unknown-linux-musl.tar.gz` - arm64: `codexel-aarch64-unknown-linux-musl.tar.gz` -Each archive contains a single entry with the platform baked into the name (e.g., `codexel-x86_64-unknown-linux-musl`), so you likely want to rename it to `codexel` after extracting it. +Each archive contains a single `codexel` binary. diff --git a/codex-cli/scripts/verify-vendor.mjs b/codex-cli/scripts/verify-vendor.mjs index 78c1d6e8601..05582ea39cc 100644 --- a/codex-cli/scripts/verify-vendor.mjs +++ b/codex-cli/scripts/verify-vendor.mjs @@ -10,6 +10,8 @@ const vendorRoot = path.join(packageRoot, "vendor"); const targets = [ "aarch64-unknown-linux-musl", "x86_64-unknown-linux-musl", + "aarch64-apple-darwin", + "x86_64-apple-darwin", "aarch64-pc-windows-msvc", "x86_64-pc-windows-msvc", ]; diff --git a/codex-rs/README.md b/codex-rs/README.md index c945b12b62a..44c5118c5c4 100644 --- a/codex-rs/README.md +++ b/codex-rs/README.md @@ -11,7 +11,7 @@ npm i -g @ixe1/codexel codexel ``` -You can also install via Homebrew (`brew install --cask codexel`) or download a platform-specific release directly from [GitHub Releases](../../releases). +You can also install via Homebrew (`brew install --cask Ixe1/tap/codexel`) or download a platform-specific release directly from [GitHub Releases](../../releases). ## Documentation quickstart diff --git a/codex-rs/tui/src/updates.rs b/codex-rs/tui/src/updates.rs index bfd9ae9edbe..b6137654fb8 100644 --- a/codex-rs/tui/src/updates.rs +++ b/codex-rs/tui/src/updates.rs @@ -64,7 +64,7 @@ const NPM_LATEST_URL: &str = "https://registry.npmjs.org/@ixe1%2Fcodexel/latest" const NPM_SOURCE_KEY: &str = "npm:@ixe1/codexel"; // We use the latest version from the cask if installation is via homebrew - homebrew does not immediately pick up the latest release and can lag behind. const HOMEBREW_CASK_URL: &str = - "https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/codexel.rb"; + "https://raw.githubusercontent.com/Ixe1/homebrew-tap/HEAD/Casks/codexel.rb"; const HOMEBREW_SOURCE_KEY: &str = "brew:codexel"; const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/Ixe1/codexel/releases/latest"; const GITHUB_SOURCE_KEY: &str = "github:Ixe1/codexel"; @@ -221,7 +221,7 @@ fn extract_version_from_cask(cask_contents: &str) -> anyhow::Result { fn extract_version_from_latest_tag(latest_tag_name: &str) -> anyhow::Result { latest_tag_name - .strip_prefix("rust-v") + .strip_prefix("codexel-v") .map(str::to_owned) .ok_or_else(|| anyhow::anyhow!("Failed to parse latest tag name '{latest_tag_name}'")) } @@ -295,7 +295,7 @@ mod tests { #[test] fn extracts_version_from_latest_tag() { assert_eq!( - extract_version_from_latest_tag("rust-v1.5.0").expect("failed to parse version"), + extract_version_from_latest_tag("codexel-v1.5.0").expect("failed to parse version"), "1.5.0" ); } diff --git a/codex-rs/tui2/src/updates.rs b/codex-rs/tui2/src/updates.rs index bfd9ae9edbe..b6137654fb8 100644 --- a/codex-rs/tui2/src/updates.rs +++ b/codex-rs/tui2/src/updates.rs @@ -64,7 +64,7 @@ const NPM_LATEST_URL: &str = "https://registry.npmjs.org/@ixe1%2Fcodexel/latest" const NPM_SOURCE_KEY: &str = "npm:@ixe1/codexel"; // We use the latest version from the cask if installation is via homebrew - homebrew does not immediately pick up the latest release and can lag behind. const HOMEBREW_CASK_URL: &str = - "https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/codexel.rb"; + "https://raw.githubusercontent.com/Ixe1/homebrew-tap/HEAD/Casks/codexel.rb"; const HOMEBREW_SOURCE_KEY: &str = "brew:codexel"; const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/Ixe1/codexel/releases/latest"; const GITHUB_SOURCE_KEY: &str = "github:Ixe1/codexel"; @@ -221,7 +221,7 @@ fn extract_version_from_cask(cask_contents: &str) -> anyhow::Result { fn extract_version_from_latest_tag(latest_tag_name: &str) -> anyhow::Result { latest_tag_name - .strip_prefix("rust-v") + .strip_prefix("codexel-v") .map(str::to_owned) .ok_or_else(|| anyhow::anyhow!("Failed to parse latest tag name '{latest_tag_name}'")) } @@ -295,7 +295,7 @@ mod tests { #[test] fn extracts_version_from_latest_tag() { assert_eq!( - extract_version_from_latest_tag("rust-v1.5.0").expect("failed to parse version"), + extract_version_from_latest_tag("codexel-v1.5.0").expect("failed to parse version"), "1.5.0" ); } diff --git a/docs/faq.md b/docs/faq.md index 6384e0cd3c5..c9d616649bb 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -45,11 +45,12 @@ Follow the quick setup in [Install & build](./install.md) and then jump into [Ge ### `brew upgrade codexel` isn't upgrading me -If you're running Codexel v0.46.0 or older, `brew upgrade codexel` will not move you to the latest version because we migrated from a Homebrew formula to a cask. To upgrade, uninstall the existing oudated formula and then install the new cask: +If Homebrew isn't picking up the latest version, ensure you're using the Codexel tap and reinstall the cask: ```bash -brew uninstall --formula codexel -brew install --cask codexel +brew update +brew uninstall --cask codexel +brew install --cask Ixe1/tap/codexel ``` After reinstalling, `brew upgrade --cask codexel` will keep future releases up to date. diff --git a/docs/releasing.md b/docs/releasing.md index 5387052e9e5..df65eb6a9eb 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -10,6 +10,7 @@ Codexel is published as `@ixe1/codexel` with prebuilt native binaries bundled in - Create and push a tag: - Stable: `codexel-vX.Y.Z` - Pre-release: `codexel-vX.Y.Z-alpha.N` or `codexel-vX.Y.Z-beta.N` +- In GitHub Actions, run the `npm-publish-codexel` workflow and pass the tag you just pushed. ### What the workflow does @@ -19,9 +20,19 @@ The `npm-publish-codexel` workflow: - Assembles `codex-cli/vendor//codex/codexel(.exe)`. - Packs an npm tarball and runs a smoke test (`codexel --help`). - Creates a GitHub Release containing the per-target binaries and npm tarball. -- Publishes `@ixe1/codexel` using npm Trusted Publishing (OIDC). +- Publishes `@ixe1/codexel` using npm Trusted Publishing (OIDC) when `publish_npm` is enabled. +- If `HOMEBREW_TAP_GITHUB_TOKEN` is set, updates the `codexel` cask in `Ixe1/homebrew-tap`. + +If you only want to publish/update Homebrew artifacts (no npm), run the `homebrew-cask-codexel` workflow manually with an existing `codexel-v*` tag. ### One-time setup Before the first publish, configure npm Trusted Publishing for `@ixe1/codexel` to trust this repository and the `npm-publish-codexel` workflow in the npm UI. + +If you want Homebrew installs (`brew install --cask Ixe1/tap/codexel`) to track releases: + +- Create the tap repository `Ixe1/homebrew-tap`. +- Add a repository secret `HOMEBREW_TAP_GITHUB_TOKEN` (a GitHub token with permission to push to `Ixe1/homebrew-tap`). + - Recommended: a fine-grained Personal Access Token scoped to `Ixe1/homebrew-tap` with `Contents: Read and write`. + - Create it in GitHub: `Settings -> Developer settings -> Personal access tokens -> Fine-grained tokens`.