rust-release #753
Workflow file for this run
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
| # Release workflow for codex-rs. | |
| # To release, follow a workflow like: | |
| # ``` | |
| # git tag -a rust-v0.1.0 -m "Release 0.1.0" | |
| # git push origin rust-v0.1.0 | |
| # ``` | |
| name: rust-release | |
| on: | |
| push: | |
| tags: | |
| - "rust-v*.*.*" | |
| concurrency: | |
| group: ${{ github.workflow }} | |
| cancel-in-progress: true | |
| jobs: | |
| tag-check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@1.92 | |
| - name: Validate tag matches Cargo.toml version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| echo "::group::Tag validation" | |
| # 1. Must be a tag and match the regex | |
| [[ "${GITHUB_REF_TYPE}" == "tag" ]] \ | |
| || { echo "❌ Not a tag push"; exit 1; } | |
| [[ "${GITHUB_REF_NAME}" =~ ^rust-v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta)(\.[0-9]+)?)?$ ]] \ | |
| || { echo "❌ Tag '${GITHUB_REF_NAME}' doesn't match expected format"; exit 1; } | |
| # 2. Extract versions | |
| tag_ver="${GITHUB_REF_NAME#rust-v}" | |
| cargo_ver="$(grep -m1 '^version' codex-rs/Cargo.toml \ | |
| | sed -E 's/version *= *"([^"]+)".*/\1/')" | |
| # 3. Compare | |
| [[ "${tag_ver}" == "${cargo_ver}" ]] \ | |
| || { echo "❌ Tag ${tag_ver} ≠ Cargo.toml ${cargo_ver}"; exit 1; } | |
| echo "✅ Tag and Cargo.toml agree (${tag_ver})" | |
| echo "::endgroup::" | |
| build: | |
| needs: tag-check | |
| name: Build - ${{ matrix.runner }} - ${{ matrix.target }} | |
| runs-on: ${{ matrix.runs_on || matrix.runner }} | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| defaults: | |
| run: | |
| working-directory: codex-rs | |
| env: | |
| CARGO_PROFILE_RELEASE_LTO: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: macos-15-xlarge | |
| target: aarch64-apple-darwin | |
| - runner: macos-15-xlarge | |
| target: x86_64-apple-darwin | |
| - runner: ubuntu-24.04 | |
| target: x86_64-unknown-linux-musl | |
| - runner: ubuntu-24.04 | |
| target: x86_64-unknown-linux-gnu | |
| - runner: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-musl | |
| - runner: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Print runner specs (Linux) | |
| if: ${{ runner.os == 'Linux' }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cpu_model="$(lscpu | awk -F: '/Model name/ {gsub(/^[ \t]+/, "", $2); print $2; exit}')" | |
| total_ram="$(awk '/MemTotal/ {printf "%.1f GiB\n", $2 / 1024 / 1024}' /proc/meminfo)" | |
| echo "Runner: ${RUNNER_NAME:-unknown}" | |
| echo "OS: $(uname -a)" | |
| echo "CPU model: ${cpu_model}" | |
| echo "Logical CPUs: $(nproc)" | |
| echo "Total RAM: ${total_ram}" | |
| echo "Disk usage:" | |
| df -h . | |
| - name: Print runner specs (macOS) | |
| if: ${{ runner.os == 'macOS' }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| total_ram="$(sysctl -n hw.memsize | awk '{printf "%.1f GiB\n", $1 / 1024 / 1024 / 1024}')" | |
| echo "Runner: ${RUNNER_NAME:-unknown}" | |
| echo "OS: $(sw_vers -productName) $(sw_vers -productVersion)" | |
| echo "Hardware model: $(sysctl -n hw.model)" | |
| echo "CPU architecture: $(uname -m)" | |
| echo "Logical CPUs: $(sysctl -n hw.logicalcpu)" | |
| echo "Physical CPUs: $(sysctl -n hw.physicalcpu)" | |
| echo "Total RAM: ${total_ram}" | |
| echo "Disk usage:" | |
| df -h . | |
| - name: Install Linux bwrap build dependencies | |
| if: ${{ runner.os == 'Linux' }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update -y | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev | |
| - name: Install UBSan runtime (musl) | |
| if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if command -v apt-get >/dev/null 2>&1; then | |
| sudo apt-get update -y | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libubsan1 | |
| fi | |
| - uses: dtolnay/rust-toolchain@1.93.0 | |
| with: | |
| targets: ${{ matrix.target }} | |
| - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} | |
| name: Use hermetic Cargo home (musl) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cargo_home="${GITHUB_WORKSPACE}/.cargo-home" | |
| mkdir -p "${cargo_home}/bin" | |
| echo "CARGO_HOME=${cargo_home}" >> "$GITHUB_ENV" | |
| echo "${cargo_home}/bin" >> "$GITHUB_PATH" | |
| : > "${cargo_home}/config.toml" | |
| - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} | |
| name: Install Zig | |
| uses: mlugg/setup-zig@v2 | |
| with: | |
| version: 0.14.0 | |
| - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} | |
| name: Install musl build tools | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: bash "${GITHUB_WORKSPACE}/.github/scripts/install-musl-build-tools.sh" | |
| - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} | |
| name: Configure rustc UBSan wrapper (musl host) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ubsan="" | |
| if command -v ldconfig >/dev/null 2>&1; then | |
| ubsan="$(ldconfig -p | grep -m1 'libubsan\.so\.1' | sed -E 's/.*=> (.*)$/\1/')" | |
| fi | |
| wrapper_root="${RUNNER_TEMP:-/tmp}" | |
| wrapper="${wrapper_root}/rustc-ubsan-wrapper" | |
| cat > "${wrapper}" <<EOF | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| if [[ -n "${ubsan}" ]]; then | |
| export LD_PRELOAD="${ubsan}\${LD_PRELOAD:+:\${LD_PRELOAD}}" | |
| fi | |
| exec "\$1" "\${@:2}" | |
| EOF | |
| chmod +x "${wrapper}" | |
| echo "RUSTC_WRAPPER=${wrapper}" >> "$GITHUB_ENV" | |
| echo "RUSTC_WORKSPACE_WRAPPER=" >> "$GITHUB_ENV" | |
| - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} | |
| name: Clear sanitizer flags (musl) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Avoid problematic aws-lc jitter entropy code path on musl builders. | |
| echo "AWS_LC_SYS_NO_JITTER_ENTROPY=1" >> "$GITHUB_ENV" | |
| target_no_jitter="AWS_LC_SYS_NO_JITTER_ENTROPY_${{ matrix.target }}" | |
| target_no_jitter="${target_no_jitter//-/_}" | |
| echo "${target_no_jitter}=1" >> "$GITHUB_ENV" | |
| # Clear global Rust flags so host/proc-macro builds don't pull in UBSan. | |
| echo "RUSTFLAGS=" >> "$GITHUB_ENV" | |
| echo "CARGO_ENCODED_RUSTFLAGS=" >> "$GITHUB_ENV" | |
| echo "RUSTDOCFLAGS=" >> "$GITHUB_ENV" | |
| # Override any runner-level Cargo config rustflags as well. | |
| echo "CARGO_BUILD_RUSTFLAGS=" >> "$GITHUB_ENV" | |
| echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV" | |
| echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS=" >> "$GITHUB_ENV" | |
| echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV" | |
| echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS=" >> "$GITHUB_ENV" | |
| sanitize_flags() { | |
| local input="$1" | |
| input="${input//-fsanitize=undefined/}" | |
| input="${input//-fno-sanitize-recover=undefined/}" | |
| input="${input//-fno-sanitize-trap=undefined/}" | |
| echo "$input" | |
| } | |
| cflags="$(sanitize_flags "${CFLAGS-}")" | |
| cxxflags="$(sanitize_flags "${CXXFLAGS-}")" | |
| echo "CFLAGS=${cflags}" >> "$GITHUB_ENV" | |
| echo "CXXFLAGS=${cxxflags}" >> "$GITHUB_ENV" | |
| - name: Cargo build | |
| shell: bash | |
| run: | | |
| cargo build --target ${{ matrix.target }} --release --timings --bin codex --bin codex-responses-api-proxy | |
| - name: Upload Cargo timings | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: cargo-timings-rust-release-${{ matrix.target }} | |
| path: codex-rs/target/**/cargo-timings/cargo-timing.html | |
| if-no-files-found: warn | |
| - if: ${{ contains(matrix.target, 'linux') }} | |
| name: Cosign Linux artifacts | |
| uses: ./.github/actions/linux-code-sign | |
| with: | |
| target: ${{ matrix.target }} | |
| artifacts-dir: ${{ github.workspace }}/codex-rs/target/${{ matrix.target }}/release | |
| - if: ${{ runner.os == 'macOS' }} | |
| name: MacOS code signing (binaries) | |
| uses: ./.github/actions/macos-code-sign | |
| with: | |
| target: ${{ matrix.target }} | |
| sign-binaries: "true" | |
| sign-dmg: "false" | |
| apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} | |
| apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} | |
| apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} | |
| - if: ${{ runner.os == 'macOS' }} | |
| name: Build macOS dmg | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| target="${{ matrix.target }}" | |
| release_dir="target/${target}/release" | |
| dmg_root="${RUNNER_TEMP}/codex-dmg-root" | |
| volname="Codex (${target})" | |
| dmg_path="${release_dir}/codex-${target}.dmg" | |
| # The previous "MacOS code signing (binaries)" step signs + notarizes the | |
| # built artifacts in `${release_dir}`. This step packages *those same* | |
| # signed binaries into a dmg. | |
| codex_binary_path="${release_dir}/codex" | |
| proxy_binary_path="${release_dir}/codex-responses-api-proxy" | |
| rm -rf "$dmg_root" | |
| mkdir -p "$dmg_root" | |
| if [[ ! -f "$codex_binary_path" ]]; then | |
| echo "Binary $codex_binary_path not found" | |
| exit 1 | |
| fi | |
| if [[ ! -f "$proxy_binary_path" ]]; then | |
| echo "Binary $proxy_binary_path not found" | |
| exit 1 | |
| fi | |
| ditto "$codex_binary_path" "${dmg_root}/codex" | |
| ditto "$proxy_binary_path" "${dmg_root}/codex-responses-api-proxy" | |
| rm -f "$dmg_path" | |
| hdiutil create \ | |
| -volname "$volname" \ | |
| -srcfolder "$dmg_root" \ | |
| -format UDZO \ | |
| -ov \ | |
| "$dmg_path" | |
| if [[ ! -f "$dmg_path" ]]; then | |
| echo "dmg $dmg_path not found after build" | |
| exit 1 | |
| fi | |
| - if: ${{ runner.os == 'macOS' }} | |
| name: MacOS code signing (dmg) | |
| uses: ./.github/actions/macos-code-sign | |
| with: | |
| target: ${{ matrix.target }} | |
| sign-binaries: "false" | |
| sign-dmg: "true" | |
| apple-certificate: ${{ secrets.APPLE_CERTIFICATE_P12 }} | |
| apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| apple-notarization-key-p8: ${{ secrets.APPLE_NOTARIZATION_KEY_P8 }} | |
| apple-notarization-key-id: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} | |
| apple-notarization-issuer-id: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} | |
| - name: Stage artifacts | |
| shell: bash | |
| run: | | |
| dest="dist/${{ matrix.target }}" | |
| mkdir -p "$dest" | |
| cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}" | |
| cp target/${{ matrix.target }}/release/codex-responses-api-proxy "$dest/codex-responses-api-proxy-${{ matrix.target }}" | |
| if [[ "${{ matrix.target }}" == *linux* ]]; then | |
| cp target/${{ matrix.target }}/release/codex.sigstore "$dest/codex-${{ matrix.target }}.sigstore" | |
| cp target/${{ matrix.target }}/release/codex-responses-api-proxy.sigstore "$dest/codex-responses-api-proxy-${{ matrix.target }}.sigstore" | |
| fi | |
| if [[ "${{ matrix.target }}" == *apple-darwin ]]; then | |
| cp target/${{ matrix.target }}/release/codex-${{ matrix.target }}.dmg "$dest/codex-${{ matrix.target }}.dmg" | |
| fi | |
| - name: Compress artifacts | |
| shell: bash | |
| run: | | |
| # Path that contains the uncompressed binaries for the current | |
| # ${{ matrix.target }} | |
| dest="dist/${{ matrix.target }}" | |
| # For compatibility with environments that lack the `zstd` tool we | |
| # additionally create a `.tar.gz` alongside every binary we publish. | |
| # The end result is: | |
| # codex-<target>.zst (existing) | |
| # codex-<target>.tar.gz (new) | |
| # 1. Produce a .tar.gz for every file in the directory *before* we | |
| # run `zstd --rm`, because that flag deletes the original files. | |
| for f in "$dest"/*; do | |
| base="$(basename "$f")" | |
| # Skip files that are already archives (shouldn't happen, but be | |
| # safe). | |
| if [[ "$base" == *.tar.gz || "$base" == *.zip || "$base" == *.dmg ]]; then | |
| continue | |
| fi | |
| # Don't try to compress signature bundles. | |
| if [[ "$base" == *.sigstore ]]; then | |
| continue | |
| fi | |
| # Create per-binary tar.gz | |
| tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base" | |
| # Also create .zst and remove the uncompressed binaries to keep | |
| # non-Windows artifact directories small. | |
| zstd -T0 -19 --rm "$dest/$base" | |
| done | |
| - uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.target }} | |
| # Upload the per-binary .zst files as well as the new .tar.gz | |
| # equivalents we generated in the previous step. | |
| path: | | |
| codex-rs/dist/${{ matrix.target }}/* | |
| build-windows: | |
| needs: tag-check | |
| uses: ./.github/workflows/rust-release-windows.yml | |
| with: | |
| release-lto: ${{ contains(github.ref_name, '-alpha') && 'thin' || 'fat' }} | |
| secrets: inherit | |
| shell-tool-mcp: | |
| name: shell-tool-mcp | |
| needs: tag-check | |
| uses: ./.github/workflows/shell-tool-mcp.yml | |
| with: | |
| release-tag: ${{ github.ref_name }} | |
| publish: true | |
| secrets: inherit | |
| release: | |
| needs: | |
| - build | |
| - build-windows | |
| - shell-tool-mcp | |
| name: release | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| actions: read | |
| outputs: | |
| version: ${{ steps.release_name.outputs.name }} | |
| tag: ${{ github.ref_name }} | |
| should_publish_npm: ${{ steps.npm_publish_settings.outputs.should_publish }} | |
| npm_tag: ${{ steps.npm_publish_settings.outputs.npm_tag }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Generate release notes from tag commit message | |
| id: release_notes | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # On tag pushes, GITHUB_SHA may be a tag object for annotated tags; | |
| # peel it to the underlying commit. | |
| commit="$(git rev-parse "${GITHUB_SHA}^{commit}")" | |
| notes_path="${RUNNER_TEMP}/release-notes.md" | |
| # Use the commit message for the commit the tag points at (not the | |
| # annotated tag message). | |
| git log -1 --format=%B "${commit}" > "${notes_path}" | |
| # Ensure trailing newline so GitHub's markdown renderer doesn't | |
| # occasionally run the last line into subsequent content. | |
| echo >> "${notes_path}" | |
| echo "path=${notes_path}" >> "${GITHUB_OUTPUT}" | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| path: dist | |
| - name: List | |
| run: ls -R dist/ | |
| # This is a temporary fix: we should modify shell-tool-mcp.yml so these | |
| # files do not end up in dist/ in the first place. | |
| - name: Delete entries from dist/ that should not go in the release | |
| run: | | |
| rm -rf dist/shell-tool-mcp* | |
| rm -rf dist/windows-binaries* | |
| # cargo-timing.html appears under multiple target-specific directories. | |
| # If included in files: dist/**, release upload races on duplicate | |
| # asset names and can fail with 404s. | |
| find dist -type f -name 'cargo-timing.html' -delete | |
| find dist -type d -empty -delete | |
| ls -R dist/ | |
| - name: Add config schema release asset | |
| run: | | |
| cp codex-rs/core/config.schema.json dist/config-schema.json | |
| - name: Define release name | |
| id: release_name | |
| run: | | |
| # Extract the version from the tag name, which is in the format | |
| # "rust-v0.1.0". | |
| version="${GITHUB_REF_NAME#rust-v}" | |
| echo "name=${version}" >> $GITHUB_OUTPUT | |
| - name: Determine npm publish settings | |
| id: npm_publish_settings | |
| env: | |
| VERSION: ${{ steps.release_name.outputs.name }} | |
| run: | | |
| set -euo pipefail | |
| version="${VERSION}" | |
| if [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "should_publish=true" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=" >> "$GITHUB_OUTPUT" | |
| elif [[ "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$ ]]; then | |
| echo "should_publish=true" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=alpha" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "should_publish=false" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| run_install: false | |
| - name: Setup Node.js for npm packaging | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| # stage_npm_packages.py requires DotSlash when staging releases. | |
| - uses: facebook/install-dotslash@v2 | |
| - name: Stage npm packages | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| ./scripts/stage_npm_packages.py \ | |
| --release-version "${{ steps.release_name.outputs.name }}" \ | |
| --package codex \ | |
| --package codex-responses-api-proxy \ | |
| --package codex-sdk | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: ${{ steps.release_name.outputs.name }} | |
| tag_name: ${{ github.ref_name }} | |
| body_path: ${{ steps.release_notes.outputs.path }} | |
| files: dist/** | |
| # Mark as prerelease only when the version has a suffix after x.y.z | |
| # (e.g. -alpha, -beta). Otherwise publish a normal release. | |
| prerelease: ${{ contains(steps.release_name.outputs.name, '-') }} | |
| - uses: facebook/dotslash-publish-release@v2 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag: ${{ github.ref_name }} | |
| config: .github/dotslash-config.json | |
| - name: Trigger developers.openai.com deploy | |
| # Only trigger the deploy if the release is not a pre-release. | |
| # The deploy is used to update the developers.openai.com website with the new config schema json file. | |
| if: ${{ !contains(steps.release_name.outputs.name, '-') }} | |
| continue-on-error: true | |
| env: | |
| DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL: ${{ secrets.DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL }} | |
| run: | | |
| if ! curl -sS -f -o /dev/null -X POST "$DEV_WEBSITE_VERCEL_DEPLOY_HOOK_URL"; then | |
| echo "::warning title=developers.openai.com deploy hook failed::Vercel deploy hook POST failed for ${GITHUB_REF_NAME}" | |
| exit 1 | |
| fi | |
| # Publish to npm using OIDC authentication. | |
| # July 31, 2025: https://github.blog/changelog/2025-07-31-npm-trusted-publishing-with-oidc-is-generally-available/ | |
| # npm docs: https://docs.npmjs.com/trusted-publishers | |
| publish-npm: | |
| # Publish to npm for stable releases and alpha pre-releases with numeric suffixes. | |
| if: ${{ needs.release.outputs.should_publish_npm == 'true' }} | |
| name: publish-npm | |
| needs: release | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write # Required for OIDC | |
| contents: read | |
| steps: | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| registry-url: "https://registry.npmjs.org" | |
| scope: "@openai" | |
| # Trusted publishing requires npm CLI version 11.5.1 or later. | |
| - name: Update npm | |
| run: npm install -g npm@latest | |
| - name: Download npm tarballs from release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| version="${{ needs.release.outputs.version }}" | |
| tag="${{ needs.release.outputs.tag }}" | |
| mkdir -p dist/npm | |
| patterns=( | |
| "codex-npm-${version}.tgz" | |
| "codex-npm-linux-*-${version}.tgz" | |
| "codex-npm-darwin-*-${version}.tgz" | |
| "codex-npm-win32-*-${version}.tgz" | |
| "codex-responses-api-proxy-npm-${version}.tgz" | |
| "codex-sdk-npm-${version}.tgz" | |
| ) | |
| for pattern in "${patterns[@]}"; do | |
| gh release download "$tag" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --pattern "$pattern" \ | |
| --dir dist/npm | |
| done | |
| # No NODE_AUTH_TOKEN needed because we use OIDC. | |
| - name: Publish to npm | |
| env: | |
| VERSION: ${{ needs.release.outputs.version }} | |
| NPM_TAG: ${{ needs.release.outputs.npm_tag }} | |
| run: | | |
| set -euo pipefail | |
| prefix="" | |
| if [[ -n "${NPM_TAG}" ]]; then | |
| prefix="${NPM_TAG}-" | |
| fi | |
| shopt -s nullglob | |
| tarballs=(dist/npm/*-"${VERSION}".tgz) | |
| if [[ ${#tarballs[@]} -eq 0 ]]; then | |
| echo "No npm tarballs found in dist/npm for version ${VERSION}" | |
| exit 1 | |
| fi | |
| for tarball in "${tarballs[@]}"; do | |
| filename="$(basename "${tarball}")" | |
| tag="" | |
| case "${filename}" in | |
| codex-npm-linux-*-"${VERSION}".tgz|codex-npm-darwin-*-"${VERSION}".tgz|codex-npm-win32-*-"${VERSION}".tgz) | |
| platform="${filename#codex-npm-}" | |
| platform="${platform%-${VERSION}.tgz}" | |
| tag="${prefix}${platform}" | |
| ;; | |
| codex-npm-"${VERSION}".tgz|codex-responses-api-proxy-npm-"${VERSION}".tgz|codex-sdk-npm-"${VERSION}".tgz) | |
| tag="${NPM_TAG}" | |
| ;; | |
| *) | |
| echo "Unexpected npm tarball: ${filename}" | |
| exit 1 | |
| ;; | |
| esac | |
| publish_cmd=(npm publish "${GITHUB_WORKSPACE}/${tarball}") | |
| if [[ -n "${tag}" ]]; then | |
| publish_cmd+=(--tag "${tag}") | |
| fi | |
| echo "+ ${publish_cmd[*]}" | |
| set +e | |
| publish_output="$("${publish_cmd[@]}" 2>&1)" | |
| publish_status=$? | |
| set -e | |
| echo "${publish_output}" | |
| if [[ ${publish_status} -eq 0 ]]; then | |
| continue | |
| fi | |
| if grep -qiE "previously published|cannot publish over|version already exists" <<< "${publish_output}"; then | |
| echo "Skipping already-published package version for ${filename}" | |
| continue | |
| fi | |
| exit "${publish_status}" | |
| done | |
| update-branch: | |
| name: Update latest-alpha-cli branch | |
| permissions: | |
| contents: write | |
| needs: release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Update latest-alpha-cli branch | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| gh api \ | |
| repos/${GITHUB_REPOSITORY}/git/refs/heads/latest-alpha-cli \ | |
| -X PATCH \ | |
| -f sha="${GITHUB_SHA}" \ | |
| -F force=true |