zx-semrel

July 23, 2026 Β· View on GitHub

Release

A zx-based release script β€” a semantic-release alternative (PoC)

Sometimes bloody enterprise forbids you from using any third-party solutions for sensitive operations (like release, deploy, and so on). Good old copy-paste comes to the rescue!

Btw, here's an adaptation for monorepos: zx-bulk-release

Requirements

  • macOS / linux
  • Node.js >= 14.13.1
  • git >= 2.0
  • zx >= 1.6.0

Key features

  • Zero dependencies
  • Zero configuration
  • Pretty fast
  • Tiny, less than 140 lines with comments
  • Reliability, safety, simplicity and maintainability (sarcasm)

Functionality

πŸš€ Usage

  1. Copy
  2. Tweak it, inject tokens, etc.
  3. Run
curl https://raw.githubusercontent.com/semrel-extra/zx-semrel/master/release.mjs > ./release.mjs
zx ./release.mjs

or like this if zx is not installed:

# Just replace GIT* env values with your own
GIT_COMMITTER_NAME=antongolub GIT_COMMITTER_EMAIL=mailbox@antongolub.ru GITHUB_TOKEN=token npx zx ./release.mjs

or just run it without any edits through npx:

# Cross your fingers for luck
GIT_COMMITTER_NAME=antongolub GIT_COMMITTER_EMAIL=mailbox@antongolub.ru GITHUB_TOKEN=token npx zx-semrel

See also the gh-actions usage example

Environment variables

Config is entirely env-driven. Booleans are on for any non-empty value. Only a GitHub token is strictly required β€” in GitHub Actions with id-token: write, npm OIDC self-enables, so a token pair is often the whole config.

Auth

VariableRequiredDefaultControls
GITHUB_TOKEN / GH_TOKENyesβ€”GitHub API (release), commit push, GitHub Packages auth
NPM_TOKENone ofβ€”npm auth β€” legacy token mode
NPM_OIDCone ofauto in Actionsnpm OIDC trusted publishing instead of a token

Publishing needs one of NPM_TOKEN / NPM_OIDC; in GitHub Actions with id-token: write OIDC self-enables (via the runner-provided ACTIONS_ID_TOKEN_REQUEST_URL).

Git commit / tag

VariableRequiredDefaultControls
GIT_COMMITTER_NAMEnoSemrel Extra BotCommitter name
GIT_COMMITTER_EMAILnosemrel-extra-bot@hotmail.comCommitter email β€” match the signing account for Verified
GIT_SIGN_KEYnoβ€”SSH private key β†’ sign the release commit & tag
GIT_BRANCHnocurrent β†’ masterBranch the release is pushed to
GH_USERnoβ€”Username in the token push URL (e.g. x-access-token)

npm publish β€” skipped entirely when package.json has "private": true

VariableRequiredDefaultControls
PKG_ALIASnopkg alias fieldExtra name to also publish under
NPM_PROVENANCEnoon with OIDCForce --provenance

Behaviour

VariableRequiredDefaultControls
PUSH_MAJOR_TAGnooffAlso move & force-push the major tag (v1)
DRY_RUNnooffStop before commit/push/publish β€” same as --dry-run
DEBUGnooffExtra logging β€” same as --debug
VERBOSEnooffVerbose shell output

npm publishing: OIDC vs legacy tokens

Since npm revoked classic tokens, the recommended way to publish from CI/CD is OIDC Trusted Publishing.

OIDC mode (priority) β€” set NPM_OIDC=true or omit NPM_TOKEN in a GitHub Actions environment with id-token: write permission. The npm CLI obtains a short-lived credential automatically; --provenance is enforced.

Legacy mode β€” provide NPM_TOKEN (granular access token, 90-day max lifetime). Used as fallback when NPM_OIDC is not set.

Auto-detection: if NPM_OIDC is not set and NPM_TOKEN is absent, OIDC is used automatically when ACTIONS_ID_TOKEN_REQUEST_URL is available (GitHub Actions with id-token: write).

OIDC limitations

  • First publish of a package cannot use OIDC β€” the initial version must be published with a token or locally, then configure trusted publishing on npmjs.com
  • Each package supports one trusted publisher at a time β€” configure it per package (and per alias) at npmjs.com β†’ Settings β†’ Trusted publishing
  • The workflow filename in trusted publisher config must match exactly (case-sensitive, .yml vs .yaml)
  • Requires npm >= 11.5.1 and Node.js >= 22.14.0
  • An existing project .npmrc with an _authToken for registry.npmjs.org will override OIDC β€” remove it to use trusted publishing
  • OIDC applies to npmjs.org only; GitHub Packages still uses GITHUB_TOKEN / GH_TOKEN

Signed release commits (opt-in)

Branch rulesets with Require signed commits reject the release commit, because zx-semrel creates it with a plain, unsigned local git commit. Set GIT_SIGN_KEY to sign the release commit and tag with SSH.

GIT_SIGN_KEY β€” the private SSH key of the committer identity (the full multi-line key, e.g. the contents of an id_ed25519 file). When set, zx-semrel writes it to a temporary 0600 file and enables SSH signing via local git config only (gpg.format ssh, user.signingkey, commit.gpgsign, tag.gpgsign) β€” it never touches your global git config. When unset, behaviour is unchanged (unsigned commit).

# .github/workflows/release.yml
env:
  GIT_COMMITTER_NAME: Semrel Extra Bot
  GIT_COMMITTER_EMAIL: bot@example.com          # must be a verified email on the account below
  GIT_SIGN_KEY: ${{ secrets.GIT_SIGN_KEY }}     # PEM-format private key, incl. the BEGIN/END lines

For GitHub to show Verified (rather than Unverified):

  • Add the matching public key to the committing account at Settings β†’ SSH and GPG keys β†’ New SSH key, choosing Key type: Signing Key (not the default Authentication Key).
  • That account must have a verified email equal to GIT_COMMITTER_EMAIL β€” GitHub matches the signature to the identity by the committer email.

Notes:

  • SSH format only for now; GPG signing is out of scope.
  • Generate a dedicated key, e.g. ssh-keygen -t ed25519 -C bot@example.com -f ./sign_key, store the private half as the GIT_SIGN_KEY secret, and register sign_key.pub as a Signing Key.
  • The bot account still needs push access to the protected branch (rulesets apply to everyone unless bypassed).

πŸ› οΈ Extras

πŸ“„ License

MIT

πŸ“Ž Refs