diff --git a/.github/actions/macos-code-sign/notary_helpers.sh b/.github/actions/macos-code-sign/notary_helpers.sh index ad9757fe3c..f2c2158c00 100644 --- a/.github/actions/macos-code-sign/notary_helpers.sh +++ b/.github/actions/macos-code-sign/notary_helpers.sh @@ -25,8 +25,7 @@ notarize_submission() { --key "$notary_key_path" \ --key-id "$APPLE_NOTARIZATION_KEY_ID" \ --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ - --output-format json \ - --wait) + --output-format json) local status submission_id status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"') @@ -37,10 +36,39 @@ notarize_submission() { exit 1 fi - echo "::notice title=Notarization::$label submission ${submission_id} completed with status ${status}" - - if [[ "$status" != "Accepted" ]]; then - echo "Notarization failed for ${label} (submission ${submission_id}, status ${status})" - exit 1 + echo "::notice title=Notarization::$label submission ${submission_id} started with status ${status}" + if [[ "$status" == "Accepted" ]]; then + return 0 fi + + local started_at=$SECONDS + local timeout_seconds="${NOTARY_WAIT_TIMEOUT_SECONDS:-7200}" + while ((SECONDS - started_at < timeout_seconds)); do + sleep 30 + + submission_json=$(xcrun notarytool info "$submission_id" \ + --key "$notary_key_path" \ + --key-id "$APPLE_NOTARIZATION_KEY_ID" \ + --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ + --output-format json) + status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"') + echo "::notice title=Notarization::$label submission ${submission_id} status ${status}" + + case "$status" in + Accepted) + return 0 + ;; + Invalid|Rejected) + xcrun notarytool log "$submission_id" \ + --key "$notary_key_path" \ + --key-id "$APPLE_NOTARIZATION_KEY_ID" \ + --issuer "$APPLE_NOTARIZATION_ISSUER_ID" || true + echo "Notarization failed for ${label} (submission ${submission_id}, status ${status})" + exit 1 + ;; + esac + done + + echo "Timed out waiting for notarization of ${label} (submission ${submission_id}, last status ${status})" + exit 1 } diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 30e16c417d..06eecae8df 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -7,6 +7,7 @@ name: rust-release on: + workflow_dispatch: push: tags: - "rust-v*.*.*" @@ -25,6 +26,11 @@ jobs: shell: bash run: | set -euo pipefail + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + echo "Manual notarization probe; skipping tag validation" + exit 0 + fi + echo "::group::Tag validation" # 1. Must be a tag and match the regex @@ -49,7 +55,7 @@ jobs: needs: tag-check name: Build - ${{ matrix.runner }} - ${{ matrix.target }} runs-on: ${{ matrix.runs_on || matrix.runner }} - timeout-minutes: 60 + timeout-minutes: 180 permissions: contents: read id-token: write @@ -69,14 +75,14 @@ jobs: 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 + # - 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -234,12 +240,12 @@ jobs: echo "CARGO_PROFILE_RELEASE_LTO: ${CARGO_PROFILE_RELEASE_LTO}" cargo build --target ${{ matrix.target }} --release --timings --bin codex --bin codex-responses-api-proxy - - name: Upload Cargo timings - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - with: - name: cargo-timings-rust-release-${{ matrix.target }} - path: codex-rs/target/**/cargo-timings/cargo-timing.html - if-no-files-found: warn + # - name: Upload Cargo timings + # uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + # 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 @@ -320,400 +326,5 @@ jobs: 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-.zst (existing) - # codex-.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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 - 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 - - argument-comment-lint-release-assets: - name: argument-comment-lint release assets - needs: tag-check - uses: ./.github/workflows/rust-release-argument-comment-lint.yml - with: - publish: true - - zsh-release-assets: - name: zsh release assets - needs: tag-check - uses: ./.github/workflows/rust-release-zsh.yml - - release: - needs: - - build - - build-windows - - argument-comment-lint-release-assets - - zsh-release-assets - 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 - with: - path: dist - - - name: List - run: ls -R dist/ - - - name: Delete entries from dist/ that should not go in the release - run: | - 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@a8198c4bff370c8506180b035930dea56dbd5288 # v5 - with: - run_install: false - - - name: Setup Node.js for npm packaging - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # 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@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2 - - name: Stage npm packages - env: - GH_TOKEN: ${{ github.token }} - RELEASE_VERSION: ${{ steps.release_name.outputs.name }} - run: | - ./scripts/stage_npm_packages.py \ - --release-version "$RELEASE_VERSION" \ - --package codex \ - --package codex-responses-api-proxy \ - --package codex-sdk - - - name: Stage installer scripts - run: | - cp scripts/install/install.sh dist/install.sh - cp scripts/install/install.ps1 dist/install.ps1 - - - name: Create GitHub Release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # 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@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag: ${{ github.ref_name }} - config: .github/dotslash-config.json - - - uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag: ${{ github.ref_name }} - config: .github/dotslash-zsh-config.json - - - uses: facebook/dotslash-publish-release@9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag: ${{ github.ref_name }} - config: .github/dotslash-argument-comment-lint-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@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - # Node 24 bundles npm >= 11.5.1, which trusted publishing requires. - node-version: 24 - registry-url: "https://registry.npmjs.org" - scope: "@openai" - - - name: Download npm tarballs from release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ needs.release.outputs.tag }} - RELEASE_VERSION: ${{ needs.release.outputs.version }} - run: | - set -euo pipefail - version="$RELEASE_VERSION" - tag="$RELEASE_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 - - winget: - name: winget - needs: release - # Only publish stable/mainline releases to WinGet; pre-releases include a - # '-' in the semver string (e.g., 1.2.3-alpha.1). - if: ${{ !contains(needs.release.outputs.version, '-') }} - # This job only invokes a GitHub Action to open/update the winget-pkgs PR; - # it does not execute Windows-only tooling, so Linux is sufficient. - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Publish to WinGet - uses: vedantmgoyal9/winget-releaser@7bd472be23763def6e16bd06cc8b1cdfab0e2fd5 - with: - identifier: OpenAI.Codex - version: ${{ needs.release.outputs.version }} - release-tag: ${{ needs.release.outputs.tag }} - fork-user: openai-oss-forks - installers-regex: '^codex-(?:x86_64|aarch64)-pc-windows-msvc\.exe\.zip$' - token: ${{ secrets.WINGET_PUBLISH_PAT }} - - 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 + # Release staging, release artifact upload, publishing, WinGet, and branch update + # steps are intentionally disabled on this branch.