zx-semrel
v1.25.1
Published
zx-based release script as semantic-release alternative (PoC)
Readme
zx-semrel
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
- Poor conventional commits analysis
CHANGELOG.mdgenerationpackage.jsonversion bumping- Git release commit creation (optionally SSH-signed)
- GitHub Release
- Package publishing to both npmjs and gh registries
🚀 Usage
- Copy
- Tweak it, inject tokens, etc.
- Run
curl https://raw.githubusercontent.com/semrel-extra/zx-semrel/master/release.mjs > ./release.mjs
zx ./release.mjsor like this if zx is not installed:
# Just replace GIT* env values with your own
GIT_COMMITTER_NAME=antongolub [email protected] GITHUB_TOKEN=token npx zx ./release.mjsor just run it without any edits through npx:
# Cross your fingers for luck
GIT_COMMITTER_NAME=antongolub [email protected] GITHUB_TOKEN=token npx zx-semrelSee 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
| Variable | Required | Default | Controls |
|---|:--:|---|---|
| GITHUB_TOKEN / GH_TOKEN | yes | — | GitHub API (release), commit push, GitHub Packages auth |
| NPM_TOKEN | one of | — | npm auth — legacy token mode |
| NPM_OIDC | one of | auto in Actions | npm 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
| Variable | Required | Default | Controls |
|---|:--:|---|---|
| GIT_COMMITTER_NAME | no | Semrel Extra Bot | Committer name |
| GIT_COMMITTER_EMAIL | no | [email protected] | Committer email — match the signing account for Verified |
| GIT_SIGN_KEY | no | — | SSH private key → sign the release commit & tag |
| GIT_BRANCH | no | current → master | Branch the release is pushed to |
| GH_USER | no | — | Username in the token push URL (e.g. x-access-token) |
npm publish — skipped entirely when package.json has "private": true
| Variable | Required | Default | Controls |
|---|:--:|---|---|
| PKG_ALIAS | no | pkg alias field | Extra name to also publish under |
| NPM_PROVENANCE | no | on with OIDC | Force --provenance |
Behaviour
| Variable | Required | Default | Controls |
|---|:--:|---|---|
| PUSH_MAJOR_TAG | no | off | Also move & force-push the major tag (v1) |
| DRY_RUN | no | off | Stop before commit/push/publish — same as --dry-run |
| DEBUG | no | off | Extra logging — same as --debug |
| VERBOSE | no | off | Verbose 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,
.ymlvs.yaml) - Requires npm >= 11.5.1 and Node.js >= 22.14.0
- An existing project
.npmrcwith an_authTokenforregistry.npmjs.orgwill 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: [email protected] # must be a verified email on the account below
GIT_SIGN_KEY: ${{ secrets.GIT_SIGN_KEY }} # PEM-format private key, incl. the BEGIN/END linesFor 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 [email protected] -f ./sign_key, store the private half as theGIT_SIGN_KEYsecret, and registersign_key.pubas a Signing Key. - The bot account still needs push access to the protected branch (rulesets apply to everyone unless bypassed).
🛠️ Extras
📄 License
📎 Refs
- npm Trusted Publishing docs
- npm classic tokens revoked
- Actually you don’t need 'semantic-release' for semantic release
- stackoverflow.com/github-oauth2-token-how-to-restrict-access-to-read-a-single-private-repo
- npmjs.com/using-private-packages-in-a-ci-cd-workflow
- https://github.com/cli/cli/issues/1425
- https://gist.github.com/Kovrinic/ea5e7123ab5c97d451804ea222ecd78a
- https://docs.github.com/en/actions/reference/authentication-in-a-workflow#permissions-for-the-github_token
- https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/
- Not invented here
