Skip to content

Publish npm 5.0.0 [38310343691-1] #1

Publish npm 5.0.0 [38310343691-1]

Publish npm 5.0.0 [38310343691-1] #1

Workflow file for this run

name: Publish npm
run-name: "Publish npm ${{ inputs.version }}${{ inputs.distinct-id != '' && format(' [{0}]', inputs.distinct-id) || '' }}"
# Stage-publishes @socketsecurity/socket-patch + the 14 platform packages to
# npm for an existing v<version> release. Dispatched two ways, both as a
# plain workflow_dispatch run:
# - by release.yml (scripts/dispatch-publish.sh), as one leg of the
# single-dispatch release fan-out — distinct-id carries the release
# run's correlation id into this run's name;
# - manually (Actions → Publish npm → Run workflow), to retry just this
# registry after a mid-release failure: fix the cause, enter the release
# version, leave distinct-id blank.
# Every run checks out refs/tags/v<version> and takes the prebuilt binaries
# from the GitHub release's assets (verified against SHA256SUMS), so a
# manual retry stages exactly what the release run would have — no rebuild
# needed. The GitHub release must already exist with all assets.
#
# Idempotent: already-published and already-staged versions are detected and
# skipped, so re-runs and partial-failure retries are safe.
#
# OIDC trusted publishing: the npm trusted publisher for every package is
# keyed on this repo + THIS file's name (publish-npm.yml). npm allows only
# ONE trusted publisher per package and matches the TOP-LEVEL workflow's
# filename, which is precisely why release.yml dispatches this file as its
# own run instead of calling it as a reusable workflow: dispatched, this
# file is always the top-level workflow, so the one registration covers
# both the release fan-out and manual retries.
on:
workflow_dispatch:
inputs:
version:
description: 'Release version (X.Y.Z; the v<version> GitHub release must exist with binaries + SHA256SUMS)'
required: true
type: string
distinct-id:
description: 'Correlation id set by release.yml to track its dispatched run — leave blank for manual runs'
required: false
default: ''
type: string
sums-digest:
description: 'Expected sha256 of the release''s SHA256SUMS asset — set by release.yml to pin the assets to what its build produced; leave blank for manual runs'
required: false
default: ''
type: string
# Serialize same-version runs: the already-published/already-staged probes
# below are check-then-act, so two CONCURRENT runs for one version could
# race them. Serialized, a duplicate (e.g. re-dispatched by a release-run
# watcher whose `gh run watch` timed out while this run was still going)
# waits behind the live run and then no-ops. NOTE: the group holds at most
# ONE waiting run — a further same-version dispatch displaces (cancels) the
# waiting duplicate, and a watcher following the displaced run reports that
# as a failure; the publish itself is unaffected (the surviving runs no-op
# or publish).
concurrency:
group: publish-npm-${{ inputs.version }}
permissions: {}
jobs:
npm-publish:
runs-on: ubuntu-latest
# Bounds how long a wedged run (hung registry call) can hold this
# workflow's per-version concurrency group before retries can proceed.
timeout-minutes: 45
permissions:
contents: read
id-token: write
steps:
- name: Validate version input
env:
VERSION: ${{ inputs.version }}
run: |
if ! printf '%s' "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::'${VERSION}' is not a plain X.Y.Z release version"
exit 1
fi
- name: Checkout release tag
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: refs/tags/v${{ inputs.version }}
persist-credentials: false
- name: Verify tag carries the requested version
env:
VERSION: ${{ inputs.version }}
run: |
CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
if [ "$CARGO_VERSION" != "$VERSION" ]; then
echo "::error::tag v${VERSION} carries workspace version ${CARGO_VERSION} — refusing to publish"
exit 1
fi
- name: Configure git for HTTPS
run: git config --global url."https://github.com/".insteadOf "ssh://git@github.com/"
- name: Download binaries from the GitHub release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
SUMS_DIGEST: ${{ inputs.sums-digest }}
run: |
mkdir artifacts
gh release download "v${VERSION}" \
--repo "$GITHUB_REPOSITORY" \
--dir artifacts \
--pattern '*.tar.gz' --pattern '*.zip' --pattern 'SHA256SUMS'
cd artifacts
# Release assets are mutable (contents:write can clobber them), and
# SHA256SUMS is itself an asset of the same release — so when the
# release run dispatched us it pinned the sums file by digest,
# binding this publish to exactly what that run built (the same
# provenance the pre-split inline jobs got from same-run
# artifacts). Manual retries leave the pin blank: integrity-only.
if [ -n "$SUMS_DIGEST" ]; then
echo "${SUMS_DIGEST} SHA256SUMS" | sha256sum -c -
fi
# -c also fails on a file that SHA256SUMS lists but the download
# missed, so this doubles as a completeness check: the sums file
# was generated from the full 14-target artifact set.
sha256sum -c SHA256SUMS
- 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
env:
VERSION: ${{ inputs.version }}
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"
elif npm view "${pkg_name}@${VERSION}" version >/dev/null 2>&1; then
# Already fully published (approved) — a clean re-run.
echo "Already published, skipping."
elif npm stage list 2>/dev/null | grep -qF "${pkg_name}@${VERSION}"; then
# Staged but not yet 2FA-approved: `npm view` can't see staged
# versions, so without this check a re-run after a mid-job
# failure could never succeed. Best-effort match on the stage
# listing (format unverified against a live staged state — if it
# misses we still fail loudly below).
echo "Already staged awaiting approval, skipping."
echo "$pkg_name" >> "${RUNNER_TEMP}/staged-packages.txt"
else
exit 1
fi
done
- name: Copy README for npm package
run: cp README.md npm/socket-patch/README.md
- name: Install main-package devDependencies
# `npm stage publish` (like `npm pack`) runs the package's `prepack`
# script, which compiles the `./schema` export with tsc — the
# `typescript` devDependency must be installed for that build.
#
# NOT `npm ci`: version-sync.sh (the offline stamp) drops the lock's
# platform-package entries for any other version, and the release's
# platform packages are not yet on the registry when it runs, so the
# lock has no entries for them and `npm ci` refuses it
# ("lock file's ...@ does not satisfy ...@X.Y.Z"). `npm install`
# tolerates the stubs and skips unresolvable optional platform
# packages (verified for both the published and unpublished-version
# states); --no-save keeps the runner from touching package.json.
working-directory: npm/socket-patch
run: npm install --no-save --ignore-scripts --no-audit --no-fund
- name: Stage-publish main package
env:
VERSION: ${{ inputs.version }}
run: |
pkg_name="@socketsecurity/socket-patch"
if npm stage publish ./npm/socket-patch --access public; then
echo "$pkg_name" >> "${RUNNER_TEMP}/staged-packages.txt"
elif npm view "${pkg_name}@${VERSION}" version >/dev/null 2>&1; then
echo "Already published, skipping."
elif npm stage list 2>/dev/null | grep -qF "${pkg_name}@${VERSION}"; then
# See the platform-package step: staged-not-approved versions are
# invisible to `npm view`; tolerate them so re-runs can succeed.
echo "Already staged awaiting approval, skipping."
echo "$pkg_name" >> "${RUNNER_TEMP}/staged-packages.txt"
else
exit 1
fi
- name: Summarize staged versions awaiting approval
if: always()
env:
VERSION: ${{ inputs.version }}
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
{
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"