Release #7
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
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Release version (e.g., 0.2.0 - no v prefix)' | |
| required: true | |
| type: string | |
| # Prevent concurrent releases | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| # Restrict permissions to minimum required | |
| permissions: | |
| contents: write | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.validate.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate version format | |
| id: validate | |
| run: | | |
| VERSION="${{ inputs.version }}" | |
| if ! echo "$VERSION" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then | |
| echo "::error::Version must be in format X.Y.Z or X.Y.Z-suffix (e.g., 0.2.0, 1.0.0-alpha.1)" | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Valid version: $VERSION" | |
| - name: Check tag does not exist | |
| run: | | |
| if git rev-parse "v${{ steps.validate.outputs.version }}" >/dev/null 2>&1; then | |
| echo "::error::Tag v${{ steps.validate.outputs.version }} already exists" | |
| exit 1 | |
| fi | |
| echo "Tag v${{ steps.validate.outputs.version }} is available" | |
| # Test gate: ensure code compiles and tests pass | |
| test: | |
| runs-on: macos-latest | |
| needs: validate | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-test- | |
| - name: Run tests | |
| run: cargo test --all-features | |
| - name: Build release binary | |
| run: cargo build --release | |
| # Security gate: must pass before release proceeds | |
| security: | |
| runs-on: ubuntu-latest | |
| needs: validate | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| ~/.cargo/bin | |
| key: ${{ runner.os }}-cargo-security-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-security- | |
| - name: Install security tools | |
| run: | | |
| command -v cargo-audit || cargo install cargo-audit --locked | |
| command -v cargo-deny || cargo install cargo-deny --locked | |
| - name: Run cargo audit | |
| run: cargo audit --deny warnings | |
| - name: Run cargo deny | |
| run: cargo deny check | |
| # Prepare release: bump version, generate changelog, create tag | |
| prepare: | |
| runs-on: ubuntu-latest | |
| needs: [validate, test, security] | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v2 | |
| with: | |
| app-id: ${{ vars.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| owner: agentic-dev3o | |
| repositories: sandbox-shell | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| ~/.cargo/bin | |
| key: ${{ runner.os }}-cargo-release-tools-v1 | |
| - name: Install release tools | |
| run: | | |
| # Install cargo-edit for version bumping | |
| command -v cargo-set-version || cargo install cargo-edit --locked | |
| # Install git-cliff for changelog generation | |
| command -v git-cliff || cargo install git-cliff --locked | |
| - name: Bump version in Cargo.toml | |
| run: | | |
| cargo set-version ${{ needs.validate.outputs.version }} | |
| cargo update -p sx | |
| - name: Generate changelog | |
| run: | | |
| git-cliff --tag v${{ needs.validate.outputs.version }} --output CHANGELOG.md | |
| - name: Commit version bump and changelog | |
| run: | | |
| git config user.name "dev3o[bot]" | |
| git config user.email "${{ vars.APP_ID }}+dev3o[bot]@users.noreply.github.com" | |
| git add Cargo.toml Cargo.lock CHANGELOG.md | |
| git commit -m "chore(release): v${{ needs.validate.outputs.version }}" | |
| git tag -a "v${{ needs.validate.outputs.version }}" -m "Release v${{ needs.validate.outputs.version }}" | |
| git push origin main | |
| git push origin "v${{ needs.validate.outputs.version }}" | |
| # Build release binaries for macOS (Intel and Apple Silicon) | |
| build: | |
| runs-on: macos-latest | |
| needs: [validate, prepare] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: | |
| - x86_64-apple-darwin | |
| - aarch64-apple-darwin | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: v${{ needs.validate.outputs.version }} | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Cache cargo | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.target }}-cargo- | |
| - name: Build release binary | |
| run: cargo build --release --target ${{ matrix.target }} | |
| - name: Create archive | |
| run: | | |
| mkdir -p dist | |
| cp target/${{ matrix.target }}/release/sx dist/ | |
| cd dist | |
| tar czf sx-${{ needs.validate.outputs.version }}-${{ matrix.target }}.tar.gz sx | |
| shasum -a 256 sx-${{ needs.validate.outputs.version }}-${{ matrix.target }}.tar.gz > sx-${{ needs.validate.outputs.version }}-${{ matrix.target }}.tar.gz.sha256 | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sx-${{ matrix.target }} | |
| path: dist/*.tar.gz* | |
| retention-days: 1 | |
| # Create GitHub release with all artifacts | |
| release: | |
| runs-on: ubuntu-latest | |
| needs: [validate, build] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: v${{ needs.validate.outputs.version }} | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: List artifacts | |
| run: ls -la artifacts/ | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ needs.validate.outputs.version }} | |
| name: v${{ needs.validate.outputs.version }} | |
| body_path: CHANGELOG.md | |
| draft: false | |
| prerelease: ${{ contains(needs.validate.outputs.version, '-') }} | |
| files: | | |
| artifacts/*.tar.gz | |
| artifacts/*.sha256 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Update Homebrew tap with new version | |
| # NOTE: This job is specific to agentic-dev3o organization. | |
| # Forks should either disable this job or update the owner/repositories values. | |
| update-tap: | |
| runs-on: ubuntu-latest | |
| needs: [validate, release] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: v${{ needs.validate.outputs.version }} | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v2 | |
| with: | |
| app-id: ${{ vars.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| owner: agentic-dev3o | |
| repositories: homebrew-sx | |
| - name: Update Homebrew formula | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| set -e | |
| VERSION="${{ needs.validate.outputs.version }}" | |
| REPO="agentic-dev3o/sandbox-shell" | |
| TAP_REPO="agentic-dev3o/homebrew-sx" | |
| echo "==> Updating formula to version ${VERSION}" | |
| # Download source tarball and calculate SHA256 | |
| TARBALL_URL="https://github.com/${REPO}/archive/refs/tags/v${VERSION}.tar.gz" | |
| echo "==> Downloading ${TARBALL_URL}" | |
| HTTP_CODE=$(curl -sL -w "%{http_code}" -o /tmp/source.tar.gz "$TARBALL_URL") | |
| if [ "$HTTP_CODE" != "200" ]; then | |
| echo "Error: Failed to download tarball (HTTP ${HTTP_CODE})" | |
| exit 1 | |
| fi | |
| SHA256=$(shasum -a 256 /tmp/source.tar.gz | cut -d' ' -f1) | |
| echo "==> SHA256: ${SHA256}" | |
| # Get current formula | |
| echo "==> Fetching current formula" | |
| gh api "repos/${TAP_REPO}/contents/Formula/sx.rb" --jq '.content' | base64 -d > /tmp/formula.rb | |
| # Update version and SHA256 in formula | |
| echo "==> Updating formula" | |
| sed -i -E "s|url \"https://github.com/${REPO}/archive/refs/tags/v[^\"]+\.tar\.gz\"|url \"${TARBALL_URL}\"|" /tmp/formula.rb | |
| sed -i -E "s|sha256 \"[a-f0-9]+\"|sha256 \"${SHA256}\"|" /tmp/formula.rb | |
| # Get current file SHA (needed for update) | |
| FILE_SHA=$(gh api "repos/${TAP_REPO}/contents/Formula/sx.rb" --jq '.sha') | |
| echo "==> Current file SHA: ${FILE_SHA}" | |
| # Commit updated formula | |
| echo "==> Committing changes" | |
| CONTENT_BASE64=$(base64 -w 0 /tmp/formula.rb) | |
| gh api "repos/${TAP_REPO}/contents/Formula/sx.rb" \ | |
| -X PUT \ | |
| -f message="sx ${VERSION}" \ | |
| -f content="${CONTENT_BASE64}" \ | |
| -f sha="$FILE_SHA" \ | |
| -f branch="main" | |
| echo "==> Formula updated successfully" |