Skip to content
Open
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
133 changes: 133 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Publishes one or more npm packages from the calling ref with npm trusted
# publishing (OIDC): no long-lived token, provenance attached, and only what
# passes verification. Idempotent: a version already on npm is skipped, so a
# failed job can be re-run without burning a version number.
#
# The calling repository needs:
# - `permissions: { contents: read, id-token: write }` on the `uses:` job.
# - A GitHub environment (default `npm-publish`) with required reviewers.
# - A trusted publisher on npmjs.com for each package, pointing at the
# caller's workflow file. A brand-new package is published by hand once:
# npm only trusts a publisher for a package that already exists.
name: npm publish (reusable)

on:
workflow_call:
inputs:
packages:
description: Package directories, space separated (`.` for a single package).
type: string
default: '.'
package-manager:
description: npm or pnpm.
type: string
default: npm
node-version:
type: string
default: '22'
verify:
description: Script that must pass before publishing (typecheck, tests, build…). Empty to skip.
type: string
default: verify
build:
description: Build script when `verify` does not build. Empty to skip.
type: string
default: ''
require-tag:
description: Require the ref to be a `v<version>` tag equal to package.json (single package only).
type: boolean
default: false
environment:
description: GitHub environment that gates the publish.
type: string
default: npm-publish
access:
description: public or restricted; empty keeps each package's publishConfig.
type: string
default: ''
outputs:
published:
description: Space-separated `name@version` list published by this run (empty if everything was already on npm).
value: ${{ jobs.publish.outputs.published }}

permissions: {}

jobs:
publish:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
id-token: write
outputs:
published: ${{ steps.publish.outputs.published }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
if: inputs.package-manager == 'pnpm'

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ inputs.node-version }}
registry-url: https://registry.npmjs.org
cache: ${{ inputs.package-manager }}

# Trusted publishing needs npm >= 11.5.1; pinned to a major so an
# unreviewed npm release cannot change how a publish behaves.
- run: npm install -g npm@11

- name: Install
env:
PM: ${{ inputs.package-manager }}
run: |
if [ "$PM" = pnpm ]; then pnpm install --frozen-lockfile; else npm ci; fi

- name: Verify
if: inputs.verify != ''
env:
PM: ${{ inputs.package-manager }}
SCRIPT: ${{ inputs.verify }}
run: "$PM" run "$SCRIPT"

- name: Build
if: inputs.build != ''
env:
PM: ${{ inputs.package-manager }}
SCRIPT: ${{ inputs.build }}
run: "$PM" run "$SCRIPT"

- name: Check the tag matches the package version
if: inputs.require-tag
env:
PACKAGES: ${{ inputs.packages }}
run: |
case "$GITHUB_REF" in refs/tags/v*) ;; *) echo "::error::Publish only runs from a v* tag (got $GITHUB_REF)."; exit 1 ;; esac
set -- $PACKAGES
[ "$#" -eq 1 ] || { echo "::error::require-tag works with a single package."; exit 1; }
pkg="$(node -p "require('./$1/package.json').version")"
[ "${GITHUB_REF_NAME#v}" = "$pkg" ] || { echo "::error::Tag $GITHUB_REF_NAME does not match package.json version $pkg."; exit 1; }

# No token: the OIDC exchange happens inside npm publish. Versions already
# on npm are skipped; npm never overwrites and neither does this step.
- name: Publish
id: publish
env:
PACKAGES: ${{ inputs.packages }}
ACCESS: ${{ inputs.access }}
run: |
published=""
for dir in $PACKAGES; do
name="$(node -p "require('./$dir/package.json').name")"
version="$(node -p "require('./$dir/package.json').version")"
if [ "$(node -p "require('./$dir/package.json').private === true")" = true ]; then
echo "$name is private: skipped."; continue
fi
if npm view "$name@$version" version >/dev/null 2>&1; then
echo "$name@$version is already on npm: skipped (retry)."; continue
fi
if [ -n "$ACCESS" ]; then (cd "$dir" && npm publish --provenance --access "$ACCESS"); else (cd "$dir" && npm publish --provenance); fi
published="$published $name@$version"
done
echo "published=${published# }" >> "$GITHUB_OUTPUT"
echo "Published:${published:- nothing new}"
98 changes: 79 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,35 +100,95 @@ Bundle multiple OpenAPI YAML files into one using Redocly CLI.

---

### `npm-publish`
### `npm-publish` (composite) — deprecated

**Composite action** (use inside a job, not as a standalone workflow)
Uses a long-lived `NPM_TOKEN`, bumps the version on every run and continues when tests fail. Do not use it in new repositories; use the reusable workflow `npm-publish.yml` below instead.

Regenerate code, build, bump version, and publish to npm.
---

**Inputs:**
- `generate-script` (optional) - npm script to run for code generation
- `build-script` (optional, default: `build`) - npm script to build
- `skip-tests` (optional, default: `false`) - Skip running tests
- `npm-token` (required) - npm authentication token
## 🌐 Reusable Workflows

**Outputs:**
- `published` - Whether package was published
- `version` - New version number
### `npm-publish.yml`

**Example:**
Publishes one or more packages with **npm trusted publishing (OIDC)**: no `NPM_TOKEN`, provenance attached, only what passes verification, and idempotent (versions already on npm are skipped, so a run can be retried).

**Inputs:** `packages` (default `.`; several, space separated), `package-manager` (`npm`|`pnpm`), `node-version` (`22`), `verify` (script, default `verify`), `build` (optional script), `require-tag` (the ref must be `v<version>` = package.json), `environment` (default `npm-publish`), `access` (`public`|`restricted`; empty keeps `publishConfig`).

**Output:** `published` — `name@version` list published by the run.

**Caller requirements:** a `npm-publish` environment with required reviewers; a trusted publisher per package on npmjs.com pointing at the caller's workflow file; the first version of a new package is published by hand (npm requires the package to exist).

```yaml
- uses: beel-es/github-actions/npm-publish@main
with:
generate-script: 'generate'
build-script: 'build'
npm-token: ${{ secrets.NPM_TOKEN }}
# .github/workflows/publish.yml
on:
push:
tags: ['v*']
permissions:
contents: read
id-token: write
jobs:
publish:
uses: beel-es/github-actions/.github/workflows/npm-publish.yml@main
with:
require-tag: true
```

---
pnpm monorepo with changesets (publishes whatever the default branch says is due):

```yaml
on:
push:
branches: [main]
paths: ['packages/*/package.json']
jobs:
publish:
uses: beel-es/github-actions/.github/workflows/npm-publish.yml@main
with:
package-manager: pnpm
packages: packages/a packages/b
```


### `npm-publish.yml`

Publica uno o varios paquetes con **trusted publishing de npm (OIDC)**: sin `NPM_TOKEN`, con provenance, solo lo que pasa la verificación, e idempotente (lo que ya está en npm se salta, así que se puede relanzar).

**Inputs:** `packages` (default `.`; varios separados por espacio), `package-manager` (`npm`|`pnpm`), `node-version` (`22`), `verify` (script, default `verify`), `build` (script opcional), `require-tag` (el ref debe ser `v<version>` = package.json), `environment` (default `npm-publish`), `access` (`public`|`restricted`; vacío respeta `publishConfig`).

**Output:** `published` — lista `nombre@versión` publicada.

**Requisitos en el repo llamador:** entorno `npm-publish` con revisores; trusted publisher en npmjs.com por paquete apuntando al fichero del workflow llamador; la primera versión de un paquete nuevo se publica a mano (npm exige que exista).

```yaml
# .github/workflows/publish.yml
on:
push:
tags: ['v*']
permissions:
contents: read
id-token: write
jobs:
publish:
uses: beel-es/github-actions/.github/workflows/npm-publish.yml@main
with:
require-tag: true
```

Monorepo con pnpm y changesets (publica lo que `master` diga que toca):

```yaml
on:
push:
branches: [master]
paths: ['packages/*/package.json']
jobs:
publish:
uses: beel-es/github-actions/.github/workflows/npm-publish.yml@main
with:
package-manager: pnpm
packages: packages/brand packages/surface
```

## 🌐 Reusable Workflows

### `sync-from-openapi.yml`

Expand Down