Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,3 @@
# golden.test.ts). EOL conversion on checkout (Windows CRLF) breaks the
# byte-for-byte comparison, so they are checked out exactly as committed.
crates/socket-patch-core/tests/fixtures/redirect/** -text

# Packagist dist allowlist (fail-closed).
#
# Packagist serves dist zips from GitHub codeload archives (git archive),
# which honor export-ignore. The Composer package manifest must live at the
# repository root (Packagist only publishes root manifests), so without this
# block the dist zip would ship the entire repository. The deny-by-default
# first line export-ignores every top-level entry, then the -export-ignore
# lines un-ignore exactly the files the Composer package needs — so any
# future top-level directory stays out of the dist zip automatically.
#
# Side effect (deliberate, but easy to miss): export-ignore applies to EVERY
# git-archive consumer, not just Packagist — GitHub's auto-generated
# "Source code (zip/tar.gz)" assets on releases and codeload tarballs
# (`npm install SocketDev/socket-patch#tag`, pip-from-archive-URL, distro
# packagers) will contain only the files allowlisted below. Anyone needing
# the full source should clone the repository or use the tagged tree.
/* export-ignore
/composer.json -export-ignore
/composer -export-ignore
/LICENSE -export-ignore
/README.md -export-ignore
30 changes: 4 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,9 @@ jobs:
exit 1
fi

# Lint the out-of-workspace packaging artifacts for the ecosystems whose setup
# / CLI-distribution we added: the RubyGems CLI launcher gem + the Bundler
# plugin gem (Ruby), the Composer CLI launcher (PHP), the Maven Central
# launcher jar (Java), and the NuGet .NET-tool launcher. Ruby, PHP, Composer,
# a Temurin JDK + Maven, and the .NET SDK are all pre-installed on the
# ubuntu-latest runner.
# Lint the out-of-workspace packaging artifacts: the RubyGems CLI launcher
# gem + the Bundler plugin gem (Ruby), and the curl|sh installer. Ruby is
# pre-installed on the ubuntu-latest runner.
lint-ecosystems:
runs-on: ubuntu-latest
timeout-minutes: 10
Expand All @@ -97,27 +94,8 @@ jobs:
ruby -c crates/socket-patch-core/src/setup/gem/templates/plugins.rb.tmpl
ruby -c crates/socket-patch-core/src/setup/gem/templates/gemspec.tmpl

- name: PHP — lint the Composer launcher + validate composer.json
# composer.json lives at the repo root (Packagist requires the
# manifest at the VCS root), so validate runs from there; the
# launcher script itself stays under composer/socket-patch/bin.
run: |
php -l composer/socket-patch/bin/socket-patch
composer validate --no-check-publish

- name: Java — build the Maven Central launcher jar
# `package` exercises compile + jar + the -sources/-javadoc plugins
# Central mandates; -Dgpg.skip because CI has no release signing key.
run: mvn --batch-mode --no-transfer-progress -f maven/socket-patch/pom.xml package -Dgpg.skip=true

- name: .NET — pack the NuGet tool launcher
# `pack` (not just build) so the PackAsTool/ToolCommandName metadata
# is validated too. Output goes to RUNNER_TEMP to keep the checkout
# clean.
run: dotnet pack nuget/socket-patch -c Release -o "$RUNNER_TEMP/nupkg"

- name: Shell — shellcheck the curl|sh installer
# install.sh is the third distribution artifact this job lints; it
# install.sh is the other distribution artifact this job lints; it
# had no coverage anywhere before the self-update work touched the
# same surface. shellcheck is pre-installed on ubuntu-latest.
run: shellcheck --shell=sh scripts/install.sh
Expand Down
209 changes: 4 additions & 205 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ name: Release
# PAT/GitHub App token, and it gives full job-graph visibility plus
# "Re-run failed jobs" retryability (every publish job is idempotent).
#
# The launcher-package jobs (rubygems, packagist, maven-central, nuget) publish
# thin launchers that download the prebuilt binary from the GitHub release at
# their own version, so they only need the GitHub release (and its SHA256SUMS)
# to exist: `needs: [version, github-release]`.
# The launcher-package job (rubygems) publishes thin launchers that download
# the prebuilt binary from the GitHub release at their own version, so it only
# needs the GitHub release (and its SHA256SUMS) to exist:
# `needs: [version, github-release]`.
#
# Credentials / deployment-environment matrix (per-registry):
# - crates.io: OIDC trusted publishing (rust-lang/crates-io-auth-action);
Expand All @@ -23,16 +23,6 @@ name: Release
# - RubyGems: OIDC trusted publishing; environment `rubygems`. One
# repo+workflow publisher per gem (`socket-patch` and
# `socket-patch-bundler`), both satisfied by one exchange.
# - Packagist: PACKAGIST_USERNAME / PACKAGIST_TOKEN in environment
# `packagist` (optional — Packagist's GitHub hook syncs
# tags on its own; the API ping is a promptness nudge).
# - Maven Central: CENTRAL_USERNAME / CENTRAL_PASSWORD (portal user token)
# + CENTRAL_GPG_PRIVATE_KEY / CENTRAL_GPG_PASSPHRASE in
# environment `maven-central` (no OIDC trusted publishing
# exists for Central as of 2026-07).
# - NuGet: OIDC trusted publishing via NuGet/login with NUGET_USER,
# plus a long-lived NUGET_API_KEY fallback; environment
# `nuget`.

on:
workflow_dispatch:
Expand Down Expand Up @@ -657,194 +647,3 @@ jobs:
exit 0
fi
gem push "socket-patch-bundler-${VERSION}.gem"

packagist-publish:
needs: [version, github-release]
if: ${{ !inputs.dry-run }}
runs-on: ubuntu-latest
# Scope the Packagist secrets to a deployment environment (zizmor
# secrets-outside-env); auto-created with no protection until configured.
environment: packagist
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

# PHP + composer are pre-installed on ubuntu-latest.
- name: Lint launcher + validate composer.json
run: |
# The package manifest lives at the REPO ROOT (Packagist only
# ingests a composer.json at the VCS repository root); the launcher
# script itself stays under composer/socket-patch/.
composer validate --no-check-publish
php -l composer/socket-patch/bin/socket-patch

# Packagist is git-tag-driven: it ingests the tagged composer.json on
# its own via the repo's GitHub hook, so tags sync anyway — this ping is
# a promptness nudge. Without credentials it is a graceful no-op.
- name: Notify Packagist of the new tag
env:
PACKAGIST_USERNAME: ${{ secrets.PACKAGIST_USERNAME }}
PACKAGIST_TOKEN: ${{ secrets.PACKAGIST_TOKEN }}
run: |
if [ -z "${PACKAGIST_USERNAME}" ] || [ -z "${PACKAGIST_TOKEN}" ]; then
echo "::notice title=Packagist sync skipped::PACKAGIST_USERNAME/PACKAGIST_TOKEN not set; Packagist will sync via its repo webhook."
exit 0
fi
curl -fsSL -XPOST -H 'content-type:application/json' \
"https://packagist.org/api/update-package?username=${PACKAGIST_USERNAME}&apiToken=${PACKAGIST_TOKEN}" \
-d '{"repository":{"url":"https://github.com/SocketDev/socket-patch"}}'

# Publishes the dependency-free launcher JAR as `dev.socket:socket-patch`.
# Like the other launcher packages it downloads the prebuilt binary from the
# GitHub release at its own version, so it only needs the GitHub release +
# SHA256SUMS to exist.
maven-central-publish:
needs: [version, github-release]
if: ${{ !inputs.dry-run }}
runs-on: ubuntu-latest
# Scope the Central portal token + GPG signing key to a deployment
# environment; auto-created with no protection until configured.
environment: maven-central
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with:
distribution: temurin
java-version: '17'
# server-id must match <publishingServerId> in pom.xml. The
# username/password values here are environment-variable NAMES —
# setup-java writes a settings.xml that resolves them from the env
# of the mvn process at deploy time, so no secret lands on disk.
server-id: central
server-username: CENTRAL_USERNAME
server-password: CENTRAL_PASSWORD
# Empty when the secret is unset; setup-java skips the GPG import
# in that case (the deploy step below also skips itself).
gpg-private-key: ${{ secrets.CENTRAL_GPG_PRIVATE_KEY }}

- name: Lint + version-check the launcher jar
working-directory: maven/socket-patch
env:
EXPECTED_VERSION: ${{ needs.version.outputs.version }}
run: |
# The project <version> is baked at the tag by
# scripts/version-sync.sh; the sed is anchored on the literal
# `<!-- x-version-sync -->` marker so plugin <version> elements
# are never matched.
pomver="$(sed -n 's|.*<version>\(.*\)</version><!-- x-version-sync -->.*|\1|p' pom.xml)"
if [ "$pomver" != "$EXPECTED_VERSION" ]; then
echo "::error::pom.xml version $pomver != release $EXPECTED_VERSION (run scripts/version-sync.sh before tagging)"
exit 1
fi
mvn --batch-mode --no-transfer-progress package -Dgpg.skip=true

- name: Publish to Maven Central
# No OIDC trusted publishing exists for Maven Central as of 2026-07
# (OSSRH is gone; the Central Portal user token is the documented
# fallback), so this authenticates with long-lived environment
# secrets — unlike the crates.io / PyPI / RubyGems / NuGet jobs.
working-directory: maven/socket-patch
env:
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.CENTRAL_GPG_PASSPHRASE }}
VERSION: ${{ needs.version.outputs.version }}
run: |
if [ -z "${CENTRAL_USERNAME}" ]; then
echo "::notice title=Maven Central publish skipped::CENTRAL_USERNAME not set; built the jar but did not deploy."
exit 0
fi
# Idempotent for "Re-run failed jobs": repo1 serves the pom once
# the version is published (and Central rejects re-uploads).
if curl -sfI "https://repo1.maven.org/maven2/dev/socket/socket-patch/${VERSION}/socket-patch-${VERSION}.pom" >/dev/null; then
echo "dev.socket:socket-patch ${VERSION} already on Maven Central; skipping."
exit 0
fi
mvn --batch-mode --no-transfer-progress deploy

# Publishes the .NET-tool launcher as `SocketSecurity.SocketPatch`. Like the
# other launcher packages it downloads the prebuilt binary from the GitHub
# release at its own version, so it only needs the GitHub release +
# SHA256SUMS to exist.
nuget-publish:
needs: [version, github-release]
if: ${{ !inputs.dry-run }}
runs-on: ubuntu-latest
# OIDC trusted publishing scoped to a deployment environment; also lets a
# maintainer gate publishing with required reviewers. Auto-created with no
# protection rules until configured.
environment: nuget
permissions:
contents: read
id-token: write
env:
# The `secrets` context is unreliable in step-level `if:` expressions;
# hoist the secret to job env and gate steps on `env.NUGET_USER`
# instead (house pattern).
NUGET_USER: ${{ secrets.NUGET_USER }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Setup .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 8.0.x

- name: Version-check + pack the .NET tool
working-directory: nuget/socket-patch
env:
EXPECTED_VERSION: ${{ needs.version.outputs.version }}
run: |
# The csproj <Version> is baked at the tag by
# scripts/version-sync.sh (the csproj has exactly one <Version>
# element).
csprojver="$(sed -n 's|.*<Version>\(.*\)</Version>.*|\1|p' SocketSecurity.SocketPatch.csproj | head -1)"
if [ "$csprojver" != "$EXPECTED_VERSION" ]; then
echo "::error::csproj Version $csprojver != release $EXPECTED_VERSION (run scripts/version-sync.sh before tagging)"
exit 1
fi
dotnet pack -c Release -o "$RUNNER_TEMP/nupkg"

- name: NuGet OIDC login (trusted publishing)
id: nuget-login
# Exchanges this job's OIDC token for a short-lived nuget.org API key
# (trusted-publishing policy: repo `SocketDev/socket-patch` +
# workflow file `release.yml` + environment `nuget`). Skipped when
# NUGET_USER is not configured; the push step then falls back to the
# long-lived NUGET_API_KEY, or skips with a notice when neither is
# set.
if: env.NUGET_USER != ''
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0
with:
user: ${{ secrets.NUGET_USER }}

- name: Push to nuget.org
env:
OIDC_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
FALLBACK_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: |
KEY="${OIDC_API_KEY:-${FALLBACK_API_KEY}}"
if [ -z "$KEY" ]; then
echo "::notice title=NuGet publish skipped::neither NUGET_USER (trusted publishing) nor NUGET_API_KEY is set; packed the tool but did not push."
exit 0
fi
# --skip-duplicate makes re-runs idempotent (a 409 for an
# already-published version is reported as a warning, not an error).
dotnet nuget push "$RUNNER_TEMP"/nupkg/*.nupkg \
--api-key "$KEY" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
35 changes: 11 additions & 24 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,6 @@ into the new version's section — see docs/releasing.md.
the workspace version. The `Release` workflow's `version` job now runs the
same script, so the publish gate and the PR gate cannot drift. Playbook:
docs/releasing.md.
- **Maven Central and NuGet distribution.** Two new install channels for the
CLI. Maven Central: `dev.socket:socket-patch`, a dependency-free launcher
jar — run via `java -jar` (fetch it with `mvn dependency:copy`) or in one
shot with JBang. NuGet: `SocketSecurity.SocketPatch`, a .NET tool —
`dotnet tool install -g SocketSecurity.SocketPatch` puts `socket-patch`
on `PATH`. Both behave like the existing gem/composer launchers: on first
run they download the version-matched prebuilt binary from the GitHub
release, verify it against the published `SHA256SUMS` (HTTPS-only,
including redirects), cache it per-user, and run it; `SOCKET_PATCH_BIN`
points them at an existing binary instead.
- **`socket-patch --update` — self-update.** Downloads the release for the
compiled target from GitHub Releases, verifies it against the published
`SHA256SUMS` before extraction, sanity-execs the staged binary, and
Expand All @@ -97,8 +87,8 @@ into the new version's section — see docs/releasing.md.
(or `SOCKET_PATCH_VERSION`) pins a version, up or down; bare `--update`
never downgrades; `--force` reinstalls. `--dry-run` is a check-only
probe (zero downloads, `updateAvailable` in the `--json` details).
Package-manager-managed installs (npm, pip, cargo, the gem/composer
launcher cache, Homebrew) are detected from the canonicalized executable
Package-manager-managed installs (npm, pip, cargo, the gem launcher
cache, Homebrew) are detected from the canonicalized executable
path and refused with that manager's own upgrade command; `--force`
overrides. `--offline` refuses up front and `--force` cannot bypass it.
Concurrent updates are single-flighted via an advisory lock; every
Expand Down Expand Up @@ -401,18 +391,15 @@ into the new version's section — see docs/releasing.md.
modes never touch the caches.

- **Release workflow consolidated into a single `release.yml`.** One
dispatch now publishes every ecosystem package — crates.io, npm, PyPI,
RubyGems (both gems, via OIDC trusted publishing), Packagist, Maven
Central, and NuGet — with the launcher-package jobs gated on the GitHub
release existing. The separate `release-ecosystems.yml` workflow is
removed (its `release: published` trigger never fired: the release is
created with `GITHUB_TOKEN`, which suppresses downstream workflow
events). `composer.json` moved to the repository root — a Packagist
requirement — with a fail-closed `.gitattributes` `export-ignore` set,
so Packagist can publish `socketsecurity/socket-patch`. Note the
`export-ignore` allowlist applies to every `git archive` consumer, so
GitHub's auto-generated "Source code" release assets now contain only
the Composer package files — clone the repo for full source.
dispatch now publishes every package — crates.io, npm, PyPI, and
RubyGems (both gems), all via OIDC trusted publishing — with the
launcher-gem job gated on the GitHub release existing. The separate
`release-ecosystems.yml` workflow is removed (its `release: published`
trigger never fired: the release is created with `GITHUB_TOKEN`, which
suppresses downstream workflow events). The CLI is distributed via
GitHub releases, npm, PyPI, crates.io, and RubyGems only — the
Composer/Packagist, Maven Central, and NuGet launcher channels drafted
earlier in this cycle were dropped before ever shipping in a release.
- `--api-url` / `--proxy-url` no longer carry clap-level defaults: with
neither flag nor env var set they parse as unset and the documented
default URLs are applied at API-client construction (after the
Expand Down
Loading
Loading