@@ -26,11 +26,15 @@ jobs:
2626 runs-on : ubuntu-latest
2727
2828 permissions :
29- contents : read
29+ # `contents: write` needed to create the v<version> tag via gh api
30+ # at the end of this job. Token is scoped to the dedicated tag step
31+ # via GH_TOKEN env; never persisted in `.git/config` (checkout keeps
32+ # persist-credentials: false so build/install steps can't reach it).
33+ contents : write
3034 id-token : write # NPM trusted publishing via OIDC
3135
3236 steps :
33- - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
37+ - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 (2026-05-20)
3438 with :
3539 persist-credentials : false
3640
@@ -200,8 +204,26 @@ jobs:
200204 - name : Install dependencies
201205 run : pnpm install --loglevel error
202206
207+ # Compile the Maven manifest extension jar so the dist build bundles it
208+ # into dist/manifest-scripts (the jar is never committed; it ships only in
209+ # the published package). Invoke build-jar.sh directly, NOT via `pnpm run`:
210+ # Socket Firewall wraps the package managers (npm/pnpm/...) it shims, so a
211+ # `pnpm run` would route the Maven wrapper's download through sfw, which
212+ # fails on the non-package fetch. Running bash directly keeps the Maven
213+ # download outside the shimmed process tree. The org action allowlist forbids
214+ # actions/setup-java, so use a JDK pre-installed on the runner image
215+ # (JAVA_HOME_17_X64), falling back to the runner's default `java`.
216+ - name : Build Maven manifest extension jar
217+ run : |
218+ if [ -n "${JAVA_HOME_17_X64:-}" ]; then
219+ export JAVA_HOME="$JAVA_HOME_17_X64"
220+ fi
221+ bash src/commands/manifest/scripts/maven-extension/build-jar.sh
222+
203223 - run : INLINED_SOCKET_CLI_PUBLISHED_BUILD=1 pnpm run build:dist
204- - run : npm publish --provenance --access public --tag "${NPM_DIST_TAG}"
224+ - name : Publish socket
225+ id : publish_socket
226+ run : npm publish --provenance --access public --tag "${NPM_DIST_TAG}"
205227 continue-on-error : true
206228 env :
207229 NPM_DIST_TAG : ${{ inputs.dist-tag }}
@@ -225,3 +247,63 @@ jobs:
225247 NPM_DIST_TAG : ${{ inputs.dist-tag }}
226248 NODE_AUTH_TOKEN : ${{ secrets.NPM_TOKEN }} # zizmor: ignore[secrets-outside-env]
227249 SOCKET_CLI_DEBUG : ${{ inputs.debug }}
250+
251+ # Create v<version> git tag at the published commit SHA after a
252+ # successful socket-package publish, idempotently. GitHub Release
253+ # Immutability ("Disallow assets and tags from being modified once a
254+ # release is published") freezes tags once bound to a Release, so:
255+ # - existing tag at same SHA → no-op
256+ # - existing tag at different SHA → hard-fail (operator recovery
257+ # required)
258+ # Gated on the first publish step (publish_socket — the `socket` npm
259+ # package) actually succeeding; the cli / cli-with-sentry publishes
260+ # use `continue-on-error: true` and don't gate the tag.
261+ #
262+ # Uses gh api (not `git push`) so the token only lives in this step's
263+ # env, never written to `.git/config` by an earlier `actions/checkout`
264+ # with persist-credentials: true (which would leak it to every later
265+ # step including `pnpm install` postinstall scripts).
266+ - name : Tag release (idempotent)
267+ if : steps.publish_socket.outcome == 'success'
268+ env :
269+ GH_TOKEN : ${{ github.token }}
270+ REPO : ${{ github.repository }}
271+ run : |
272+ PUBLISHED_SHA=$(git rev-parse HEAD)
273+ PUBLISHED_VERSION=$(node -p "require('./package.json').version")
274+ TAG="v$PUBLISHED_VERSION"
275+
276+ # Look up any existing tag ref. gh api exits non-zero on 404 (tag
277+ # absent) and writes the error body to stdout, so branch on the
278+ # exit code — never on whether stdout is empty. EXISTING_JSON is
279+ # only valid JSON when the call succeeded.
280+ if EXISTING_JSON=$(gh api "repos/$REPO/git/ref/tags/$TAG" 2>/dev/null); then
281+ # The ref's object is either a commit (lightweight tag) or a tag
282+ # object (annotated/signed tag, e.g. the hand-created `git tag -s`
283+ # tags). For an annotated tag, object.sha is the tag-object SHA,
284+ # not the commit — dereference it via git/tags to get the commit
285+ # the tag actually points at before comparing.
286+ REF_TYPE=$(echo "$EXISTING_JSON" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).object.type")
287+ REF_OBJECT_SHA=$(echo "$EXISTING_JSON" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).object.sha")
288+ if [ "$REF_TYPE" = "tag" ]; then
289+ EXISTING_SHA=$(gh api "repos/$REPO/git/tags/$REF_OBJECT_SHA" --jq '.object.sha')
290+ else
291+ EXISTING_SHA="$REF_OBJECT_SHA"
292+ fi
293+ if [ "$EXISTING_SHA" = "$PUBLISHED_SHA" ]; then
294+ echo "Tag $TAG already exists at $PUBLISHED_SHA — no-op."
295+ exit 0
296+ fi
297+ echo "::error::Tag $TAG exists at $EXISTING_SHA but publish SHA is $PUBLISHED_SHA."
298+ echo "::error::Release immutability is enabled; this requires manual recovery:"
299+ echo "::error:: 1. Delete any GitHub Release tied to $TAG"
300+ echo "::error:: 2. Delete the tag via the API"
301+ echo "::error:: 3. Re-run this workflow"
302+ exit 1
303+ fi
304+
305+ gh api "repos/$REPO/git/refs" \
306+ -X POST \
307+ -f "ref=refs/tags/$TAG" \
308+ -f "sha=$PUBLISHED_SHA"
309+ echo "Created tag $TAG at $PUBLISHED_SHA"
0 commit comments