Release #31
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: | |
| dry-run: | |
| description: 'Dry run (build only, skip publish)' | |
| type: boolean | |
| default: false | |
| permissions: {} | |
| jobs: | |
| version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.read.outputs.VERSION }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Read version from Cargo.toml | |
| id: read | |
| run: | | |
| VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') | |
| echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Release version: $VERSION" | |
| - name: Check tag does not exist | |
| run: | | |
| VERSION="${{ steps.read.outputs.VERSION }}" | |
| if git rev-parse "v${VERSION}" >/dev/null 2>&1; then | |
| echo "::error::Tag v${VERSION} already exists. Bump the version in a PR first." | |
| exit 1 | |
| fi | |
| - name: Check CHANGELOG.md has entry for version | |
| run: | | |
| VERSION="${{ steps.read.outputs.VERSION }}" | |
| if [ ! -f CHANGELOG.md ]; then | |
| echo "::error::CHANGELOG.md does not exist at the repository root." | |
| exit 1 | |
| fi | |
| # Accept either `## [X.Y.Z]` or `## X.Y.Z` headings, with an | |
| # optional trailing space (followed by `— DATE`) or end-of-line. | |
| if ! grep -qE "^## \[?${VERSION}\]?( |$)" CHANGELOG.md; then | |
| echo "::error::CHANGELOG.md is missing an entry for version ${VERSION}." | |
| echo "::error::Add a heading like \`## [${VERSION}] — $(date +%Y-%m-%d)\` describing the release before re-running." | |
| exit 1 | |
| fi | |
| build: | |
| needs: version | |
| strategy: | |
| matrix: | |
| include: | |
| - target: aarch64-apple-darwin | |
| runner: macos-14 | |
| archive: tar.gz | |
| build-tool: cargo | |
| - target: x86_64-apple-darwin | |
| runner: macos-14 | |
| archive: tar.gz | |
| build-tool: cargo | |
| - target: x86_64-unknown-linux-gnu | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| - target: x86_64-unknown-linux-musl | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| - target: aarch64-unknown-linux-gnu | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| - target: aarch64-unknown-linux-musl | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| - target: x86_64-pc-windows-msvc | |
| runner: windows-latest | |
| archive: zip | |
| build-tool: cargo | |
| - target: i686-pc-windows-msvc | |
| runner: windows-latest | |
| archive: zip | |
| build-tool: cargo | |
| - target: aarch64-pc-windows-msvc | |
| runner: windows-latest | |
| archive: zip | |
| build-tool: cargo | |
| - target: aarch64-linux-android | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| - target: arm-unknown-linux-gnueabihf | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| - target: arm-unknown-linux-musleabihf | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| - target: i686-unknown-linux-gnu | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| - target: i686-unknown-linux-musl | |
| runner: ubuntu-latest | |
| archive: tar.gz | |
| build-tool: cross | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| # rustup is pre-installed on GitHub-hosted runners. `rustup show` | |
| # reads rust-toolchain.toml in the repo root, then installs the | |
| # pinned channel + listed components if missing. The dtolnay action | |
| # cannot auto-detect the channel when pinned by SHA (it normally | |
| # parses it from the ref name), so we go through rustup directly. | |
| run: | | |
| rustup show | |
| rustup target add ${{ matrix.target }} | |
| - name: Install cross | |
| if: matrix.build-tool == 'cross' | |
| run: cargo install --locked --version =0.2.5 cross | |
| - name: Build (cargo) | |
| if: matrix.build-tool == 'cargo' | |
| run: cargo build --release --target ${{ matrix.target }} | |
| - name: Build (cross) | |
| if: matrix.build-tool == 'cross' | |
| run: cross build --release --target ${{ matrix.target }} | |
| - name: Package (unix) | |
| if: matrix.archive == 'tar.gz' | |
| run: | | |
| cd target/${{ matrix.target }}/release | |
| tar czf ../../../socket-patch-${{ matrix.target }}.tar.gz socket-patch | |
| cd ../../.. | |
| - name: Package (windows) | |
| if: matrix.archive == 'zip' | |
| shell: pwsh | |
| run: | | |
| Compress-Archive -Path "target/${{ matrix.target }}/release/socket-patch.exe" -DestinationPath "socket-patch-${{ matrix.target }}.zip" | |
| - name: Upload artifact (tar.gz) | |
| if: matrix.archive == 'tar.gz' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: socket-patch-${{ matrix.target }} | |
| path: socket-patch-${{ matrix.target }}.tar.gz | |
| - name: Upload artifact (zip) | |
| if: matrix.archive == 'zip' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: socket-patch-${{ matrix.target }} | |
| path: socket-patch-${{ matrix.target }}.zip | |
| tag: | |
| needs: [version, build] | |
| if: ${{ !inputs.dry-run }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Create and push tag | |
| run: | | |
| TAG="v${{ needs.version.outputs.version }}" | |
| git tag "$TAG" | |
| git push origin "$TAG" | |
| github-release: | |
| needs: [version, build, tag] | |
| if: ${{ !inputs.dry-run }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Generate SHA256SUMS | |
| run: | | |
| cd artifacts | |
| # Hash every release artifact (tar.gz + zip) so install.sh can verify | |
| # the binary before extraction. Sorted output keeps the file stable. | |
| sha256sum *.tar.gz *.zip 2>/dev/null | sort > SHA256SUMS | |
| cat SHA256SUMS | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="v${{ needs.version.outputs.version }}" | |
| gh release create "$TAG" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --generate-notes \ | |
| artifacts/* | |
| cargo-publish: | |
| needs: [version, build, tag] | |
| if: ${{ !inputs.dry-run }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust | |
| # rustup is pre-installed on GitHub-hosted runners. `rustup show` | |
| # reads rust-toolchain.toml in the repo root, then installs the | |
| # pinned channel + listed components if missing. | |
| run: rustup show | |
| - name: Authenticate with crates.io | |
| id: crates-io-auth | |
| uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1.0.3 | |
| - name: Publish socket-patch-core | |
| run: cargo publish -p socket-patch-core | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }} | |
| - name: Wait for crates.io index update | |
| run: sleep 30 | |
| - name: Copy README for CLI crate | |
| run: cp README.md crates/socket-patch-cli/README.md | |
| - name: Publish socket-patch-cli | |
| run: cargo publish -p socket-patch-cli | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }} | |
| npm-publish: | |
| needs: [version, build, tag] | |
| if: ${{ !inputs.dry-run }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Configure git for HTTPS | |
| run: git config --global url."https://github.com/".insteadOf "ssh://git@github.com/" | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Setup Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '22.22.1' | |
| registry-url: 'https://registry.npmjs.org' | |
| package-manager-cache: false | |
| - name: Update npm for staged publishing | |
| run: npm install -g npm@11.15.0 | |
| - name: Stage binaries into platform packages | |
| run: | | |
| # Unix platforms: extract binary into each platform package directory | |
| stage_unix() { | |
| local artifact="$1" pkg_dir="$2" | |
| tar xzf "artifacts/${artifact}.tar.gz" -C "${pkg_dir}/" | |
| } | |
| # Windows platforms: extract .exe into each platform package directory | |
| stage_win() { | |
| local artifact="$1" pkg_dir="$2" | |
| unzip -o "artifacts/${artifact}.zip" -d "${pkg_dir}/" | |
| } | |
| stage_unix socket-patch-aarch64-apple-darwin npm/socket-patch-darwin-arm64 | |
| stage_unix socket-patch-x86_64-apple-darwin npm/socket-patch-darwin-x64 | |
| stage_unix socket-patch-x86_64-unknown-linux-gnu npm/socket-patch-linux-x64-gnu | |
| stage_unix socket-patch-x86_64-unknown-linux-musl npm/socket-patch-linux-x64-musl | |
| stage_unix socket-patch-aarch64-unknown-linux-gnu npm/socket-patch-linux-arm64-gnu | |
| stage_unix socket-patch-aarch64-unknown-linux-musl npm/socket-patch-linux-arm64-musl | |
| stage_unix socket-patch-arm-unknown-linux-gnueabihf npm/socket-patch-linux-arm-gnu | |
| stage_unix socket-patch-arm-unknown-linux-musleabihf npm/socket-patch-linux-arm-musl | |
| stage_unix socket-patch-i686-unknown-linux-gnu npm/socket-patch-linux-ia32-gnu | |
| stage_unix socket-patch-i686-unknown-linux-musl npm/socket-patch-linux-ia32-musl | |
| stage_unix socket-patch-aarch64-linux-android npm/socket-patch-android-arm64 | |
| stage_win socket-patch-x86_64-pc-windows-msvc npm/socket-patch-win32-x64 | |
| stage_win socket-patch-i686-pc-windows-msvc npm/socket-patch-win32-ia32 | |
| stage_win socket-patch-aarch64-pc-windows-msvc npm/socket-patch-win32-arm64 | |
| - name: Stage-publish platform packages | |
| id: stage-platform | |
| run: | | |
| : > "${RUNNER_TEMP}/staged-packages.txt" | |
| for pkg_dir in npm/socket-patch-*/; do | |
| pkg_name="@socketsecurity/$(basename "$pkg_dir")" | |
| echo "Staging ${pkg_name}..." | |
| if npm stage publish "./${pkg_dir}" --access public; then | |
| echo "$pkg_name" >> "${RUNNER_TEMP}/staged-packages.txt" | |
| else | |
| if npm view "${pkg_name}@${{ needs.version.outputs.version }}" version >/dev/null 2>&1; then | |
| echo "Already published, skipping." | |
| else | |
| exit 1 | |
| fi | |
| fi | |
| done | |
| - name: Copy README for npm package | |
| run: cp README.md npm/socket-patch/README.md | |
| - name: Stage-publish main package | |
| run: | | |
| pkg_name="@socketsecurity/socket-patch" | |
| if npm stage publish ./npm/socket-patch --access public; then | |
| echo "$pkg_name" >> "${RUNNER_TEMP}/staged-packages.txt" | |
| else | |
| if npm view "${pkg_name}@${{ needs.version.outputs.version }}" version >/dev/null 2>&1; then | |
| echo "Already published, skipping." | |
| else | |
| exit 1 | |
| fi | |
| fi | |
| - name: Summarize staged versions awaiting approval | |
| if: always() | |
| run: | | |
| STAGED_FILE="${RUNNER_TEMP}/staged-packages.txt" | |
| if [ ! -s "$STAGED_FILE" ]; then | |
| echo "No packages staged this run (all versions already published or publish step failed before staging)." >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| VERSION="${{ needs.version.outputs.version }}" | |
| { | |
| echo "## npm staged versions awaiting approval" | |
| echo "" | |
| echo "Version \`${VERSION}\` is staged on npm. A maintainer must approve each package with 2FA before it becomes installable." | |
| echo "" | |
| echo "**Approve platform packages first**, then the main \`@socketsecurity/socket-patch\` package, so install-time \`optionalDependencies\` resolution sees the platform binaries already live." | |
| echo "" | |
| echo "**Approve from the web** (signs in + 2FA prompts inline):" | |
| echo "" | |
| echo "- Org dashboard: <https://www.npmjs.com/settings/socketsecurity/staged-packages>" | |
| echo "" | |
| echo "Per-package review pages:" | |
| echo "" | |
| while IFS= read -r pkg_name; do | |
| [ -z "$pkg_name" ] && continue | |
| # npmjs.com renders staged versions inline on the package page; | |
| # the access page exposes the "Staged" tab and the approve button. | |
| echo "- \`${pkg_name}@${VERSION}\` — <https://www.npmjs.com/package/${pkg_name}/access>" | |
| done < "$STAGED_FILE" | |
| echo "" | |
| echo "**Approve from the CLI** (requires \`npm@11.15.0+\` locally, signed in to the \`socketsecurity\` org with 2FA):" | |
| echo "" | |
| echo '```sh' | |
| echo "npm stage list" | |
| echo "# then, for each stage id printed above (platform packages first, main package last):" | |
| echo "npm stage approve <stage-id>" | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # Mirror to step log so it's also visible in the raw run output. | |
| echo "::notice title=npm staged versions awaiting approval::Review and approve at https://www.npmjs.com/settings/socketsecurity/staged-packages" | |
| pypi-publish: | |
| needs: [version, build, tag] | |
| if: ${{ !inputs.dry-run }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Setup Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: '3.12.13' | |
| - name: Copy README for PyPI package | |
| run: cp README.md pypi/socket-patch/README.md | |
| - name: Build platform wheels | |
| run: | | |
| VERSION="${{ needs.version.outputs.version }}" | |
| python scripts/build-pypi-wheels.py --version "$VERSION" --artifacts artifacts --dist dist | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 | |
| with: | |
| packages-dir: dist/ |