Skip to content

Publish NPM Packages #7

Publish NPM Packages

Publish NPM Packages #7

name: Publish NPM Packages
on:
workflow_dispatch:
inputs:
packages:
description: "Comma-separated package paths (default: auto-discover packages/*)"
required: false
default: ""
type: string
access:
description: "npm access level (public or restricted)"
required: true
default: public
type: choice
options:
- public
- restricted
concurrency:
group: publish-npm-packages
cancel-in-progress: false
permissions:
contents: read
id-token: write
jobs:
resolve-packages:
name: Resolve Packages
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.resolve.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Resolve package list
id: resolve
shell: bash
run: |
python3 - <<'PY' > packages.json
import glob
import json
import os
raw = os.environ.get("INPUT_PACKAGES", "").strip()
packages = []
if raw:
for item in raw.split(","):
p = item.strip().rstrip("/")
if p:
packages.append(p)
else:
for pkg_json in sorted(glob.glob("packages/*/package.json")):
directory = os.path.dirname(pkg_json)
if "/node_modules/" in directory:
continue
packages.append(directory)
seen = set()
packages = [p for p in packages if not (p in seen or seen.add(p))]
if not packages:
raise SystemExit("No publishable packages found (set workflow input `packages` to override).")
print(json.dumps({"include": [{"relativePath": p} for p in packages]}))
PY
echo "Resolved packages:"
cat packages.json
echo ""
MATRIX="$(cat packages.json)"
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
env:
INPUT_PACKAGES: ${{ inputs.packages }}
publish:
name: Publish
runs-on: ubuntu-latest
needs: [resolve-packages]
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.resolve-packages.outputs.matrix) }}
max-parallel: 1
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Validate package path
shell: bash
run: |
test -f "${{ matrix.relativePath }}/package.json"
echo "Publishing package at: ${{ matrix.relativePath }}"
- name: Read package metadata
id: meta
working-directory: ${{ matrix.relativePath }}
shell: bash
run: |
python3 - <<'PY' >> "$GITHUB_OUTPUT"
import json
with open("package.json", "r", encoding="utf-8") as f:
pkg = json.load(f)
print(f"name={pkg.get('name', '')}")
print(f"version={pkg.get('version', '')}")
print(f"private={str(pkg.get('private', False)).lower()}")
PY
- name: Validate package metadata
if: ${{ steps.meta.outputs.private != 'true' }}
shell: bash
run: |
test -n "${{ steps.meta.outputs.name }}"
test -n "${{ steps.meta.outputs.version }}"
- name: Skip private package
if: ${{ steps.meta.outputs.private == 'true' }}
run: 'echo "Skipping private package: ${{ steps.meta.outputs.name }}"'
- name: Check if version already published
if: ${{ steps.meta.outputs.private != 'true' }}
id: exists
shell: bash
run: |
set +e
npm view "${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }}" version >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Already published: ${{ steps.meta.outputs.name }}@${{ steps.meta.outputs.version }}"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Install dependencies
if: ${{ steps.meta.outputs.private != 'true' && steps.exists.outputs.exists != 'true' }}
working-directory: ${{ matrix.relativePath }}
run: npm install
- name: Build (if present)
if: ${{ steps.meta.outputs.private != 'true' && steps.exists.outputs.exists != 'true' }}
working-directory: ${{ matrix.relativePath }}
run: npm run build --if-present
- name: Publish to npm
if: ${{ steps.meta.outputs.private != 'true' && steps.exists.outputs.exists != 'true' }}
working-directory: ${{ matrix.relativePath }}
run: npm publish --provenance --access "${{ inputs.access }}"