From d96961d567ba84e80efbeccb3be7186f7c837c0b Mon Sep 17 00:00:00 2001 From: Paul Lewis Date: Sun, 21 Dec 2025 11:53:06 +0000 Subject: [PATCH 1/4] Add Homebrew cask workflow and macOS artifacts --- .github/workflows/homebrew-cask-codexel.yml | 166 ++++++++++++++++++++ .github/workflows/npm-publish-codexel.yml | 98 ++++++++++-- README.md | 6 +- codex-cli/scripts/verify-vendor.mjs | 2 + codex-rs/README.md | 2 +- codex-rs/tui/src/updates.rs | 6 +- codex-rs/tui2/src/updates.rs | 6 +- docs/faq.md | 7 +- docs/releasing.md | 13 +- 9 files changed, 280 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/homebrew-cask-codexel.yml diff --git a/.github/workflows/homebrew-cask-codexel.yml b/.github/workflows/homebrew-cask-codexel.yml new file mode 100644 index 00000000000..dcd0eb60a9e --- /dev/null +++ b/.github/workflows/homebrew-cask-codexel.yml @@ -0,0 +1,166 @@ +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 + 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: Checkout Homebrew tap + if: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }} + uses: actions/checkout@v6 + with: + repository: Ixe1/homebrew-tap + token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + path: homebrew-tap + + - name: Update cask + if: ${{ secrets.HOMEBREW_TAP_GITHUB_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 +330,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 +343,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 && secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }} + uses: actions/checkout@v6 + with: + repository: ${{ env.HOMEBREW_TAP_REPO }} + token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + path: homebrew-tap + + - name: Update Homebrew cask + if: ${{ inputs.update_homebrew && secrets.HOMEBREW_TAP_GITHUB_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`. From 26b12d4bc7da1ebf3546ceb28152b41597abb589 Mon Sep 17 00:00:00 2001 From: Paul Lewis Date: Sun, 21 Dec 2025 11:55:01 +0000 Subject: [PATCH 2/4] Fix YAML heredoc indentation --- .github/workflows/homebrew-cask-codexel.yml | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/homebrew-cask-codexel.yml b/.github/workflows/homebrew-cask-codexel.yml index dcd0eb60a9e..6b23dfa188e 100644 --- a/.github/workflows/homebrew-cask-codexel.yml +++ b/.github/workflows/homebrew-cask-codexel.yml @@ -132,24 +132,24 @@ jobs: mkdir -p homebrew-tap/Casks cat > homebrew-tap/Casks/codexel.rb < Date: Sun, 21 Dec 2025 11:57:26 +0000 Subject: [PATCH 3/4] Fix rust-ci job if expressions --- .github/workflows/rust-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 1da23b5938f..1af0bf2f4ae 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -53,7 +53,7 @@ jobs: name: Format / etc runs-on: ubuntu-24.04 needs: changed - if: ${{ (needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push') && !(github.event_name == 'pull_request' && startsWith(matrix.runner, 'macos')) }} + if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }} defaults: run: working-directory: codex-rs @@ -71,7 +71,7 @@ jobs: name: cargo shear runs-on: ubuntu-24.04 needs: changed - if: ${{ (needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push') && !(github.event_name == 'pull_request' && startsWith(matrix.runner, 'macos')) }} + if: ${{ needs.changed.outputs.codex == 'true' || needs.changed.outputs.workflows == 'true' || github.event_name == 'push' }} defaults: run: working-directory: codex-rs From 4819c91da4b2dc5da2a86a845de225b627e929ad Mon Sep 17 00:00:00 2001 From: Paul Lewis Date: Sun, 21 Dec 2025 12:00:24 +0000 Subject: [PATCH 4/4] Fix workflows: avoid secrets in if --- .github/workflows/homebrew-cask-codexel.yml | 19 +++++++++++++++---- .github/workflows/npm-publish-codexel.yml | 7 ++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/homebrew-cask-codexel.yml b/.github/workflows/homebrew-cask-codexel.yml index 6b23dfa188e..eb26bfc2456 100644 --- a/.github/workflows/homebrew-cask-codexel.yml +++ b/.github/workflows/homebrew-cask-codexel.yml @@ -69,6 +69,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + env: + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} steps: - name: Validate tag shell: bash @@ -112,16 +114,25 @@ jobs: 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: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }} + if: ${{ env.TAP_TOKEN != '' }} uses: actions/checkout@v6 with: repository: Ixe1/homebrew-tap - token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + token: ${{ env.TAP_TOKEN }} path: homebrew-tap - name: Update cask - if: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }} + if: ${{ env.TAP_TOKEN != '' }} shell: bash run: | set -euo pipefail @@ -152,7 +163,7 @@ jobs: EOF - name: Commit and push - if: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }} + if: ${{ env.TAP_TOKEN != '' }} working-directory: homebrew-tap shell: bash run: | diff --git a/.github/workflows/npm-publish-codexel.yml b/.github/workflows/npm-publish-codexel.yml index c04b348b0ff..cf4f84e4c5b 100644 --- a/.github/workflows/npm-publish-codexel.yml +++ b/.github/workflows/npm-publish-codexel.yml @@ -294,6 +294,7 @@ jobs: contents: write env: HOMEBREW_TAP_REPO: Ixe1/homebrew-tap + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} steps: - name: Download build artifacts uses: actions/download-artifact@v7 @@ -349,15 +350,15 @@ jobs: generate_release_notes: true - name: Checkout Homebrew tap - if: ${{ inputs.update_homebrew && secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }} + if: ${{ inputs.update_homebrew && env.TAP_TOKEN != '' }} uses: actions/checkout@v6 with: repository: ${{ env.HOMEBREW_TAP_REPO }} - token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + token: ${{ env.TAP_TOKEN }} path: homebrew-tap - name: Update Homebrew cask - if: ${{ inputs.update_homebrew && secrets.HOMEBREW_TAP_GITHUB_TOKEN != '' }} + if: ${{ inputs.update_homebrew && env.TAP_TOKEN != '' }} shell: bash run: | set -euo pipefail