Skip to content

Publish to Public NPM #37

Publish to Public NPM

Publish to Public NPM #37

name: Publish to Public NPM
on:
workflow_dispatch:
inputs:
core_version:
description: "Core package version (leave empty to use package.json version)"
required: false
type: string
react_version:
description: "React package version (leave empty to use package.json version)"
required: false
type: string
packages:
description: "Packages to publish"
required: true
type: choice
default: "both"
options:
- "both"
- "core"
- "react"
npm_tag:
description: "NPM dist-tag (auto = detect from version)"
required: false
type: choice
default: "latest"
options:
- "auto"
- "latest"
- "beta"
- "alpha"
- "next"
dry_run:
description: "Dry run (preview without publishing)"
required: false
type: boolean
default: true
skip_slack_notification:
description: "Skip Slack notification"
required: false
type: boolean
default: false
permissions:
contents: write
id-token: write
jobs:
publish:
name: Build & Publish to Public NPM
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
version: 10.6.5
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "20"
cache: "pnpm"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm to latest version
run: npm install -g npm@latest
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build packages
run: pnpm run build
- name: Detect versions and npm tags
id: detect
run: |
CORE_VERSION=$(node -p "require('./packages/core/package.json').version")
REACT_VERSION=$(node -p "require('./packages/react/package.json').version")
determine_tag() {
local version=$1
if [[ "$version" == *"alpha"* ]]; then
echo "alpha"
elif [[ "$version" == *"beta"* ]]; then
echo "beta"
elif [[ "$version" == *"rc"* ]]; then
echo "next"
else
echo "latest"
fi
}
CORE_TAG=$(determine_tag "$CORE_VERSION")
REACT_TAG=$(determine_tag "$REACT_VERSION")
echo "Detected package versions and tags from package:"
echo "Core Version: $CORE_VERSION"
echo "React Version: $REACT_VERSION"
echo "Core Tag: ${CORE_TAG}"
echo "React Tag: ${REACT_TAG}"
echo "Setting environment variables: core_version, react_version, core_npm_tag & react_npm_tag"
echo "core_version=${CORE_VERSION}" >> $GITHUB_ENV
echo "react_version=${REACT_VERSION}" >> $GITHUB_ENV
echo "core_npm_tag=${CORE_TAG}" >> $GITHUB_ENV
echo "react_npm_tag=${REACT_TAG}" >> $GITHUB_ENV
- name: Set finalized versions, tags and packages to publish
id: final
env:
CORE_VER: ${{ github.event.inputs.core_version }}
REACT_VER: ${{ github.event.inputs.react_version }}
NPM_TAG_INPUT: ${{ github.event.inputs.npm_tag }}
PACKAGES_INPUT: ${{ github.event.inputs.packages }}
run: |
# Validation(Sanitize) function, exit on invalid input
validate_input() {
local input="$1"
# Allow empty input
if [[ -z "$input" ]]; then
echo ""
return 0
fi
if [[ "$input" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "$input"
else
echo "Invalid input: $input" >&2
exit 1
fi
}
# Validate user inputs for malicious content, exit if invalid
validate_input "$CORE_VER" || exit 1
validate_input "$REACT_VER" || exit 1
validate_input "$NPM_TAG_INPUT" || exit 1
validate_input "$PACKAGES_INPUT" || exit 1
if [ -z "$CORE_VER" ]; then
CORE_VER="$core_version"
fi
if [ -z "$REACT_VER" ]; then
REACT_VER="$react_version"
fi
if [ "$NPM_TAG_INPUT" = "auto" ] || [ -z "$NPM_TAG_INPUT" ]; then
CORE_TAG="$core_npm_tag"
REACT_TAG="$react_npm_tag"
else
CORE_TAG="$NPM_TAG_INPUT"
REACT_TAG="$NPM_TAG_INPUT"
fi
# Assign final tag values into environment variables
echo "CORE_VER=${CORE_VER}" >> $GITHUB_ENV
echo "REACT_VER=${REACT_VER}" >> $GITHUB_ENV
echo "CORE_TAG=${CORE_TAG}" >> $GITHUB_ENV
echo "REACT_TAG=${REACT_TAG}" >> $GITHUB_ENV
echo "PACKAGES_INPUT=${PACKAGES_INPUT}" >> $GITHUB_ENV
echo "Publishing:"
echo "Core: ${CORE_VER} @ ${CORE_TAG}"
echo "React: ${REACT_VER} @ ${REACT_TAG}"
echo "Packages: ${PACKAGES_INPUT}"
- name: Update Core package for public npm
if: |
(env.PACKAGES_INPUT == 'both' || env.PACKAGES_INPUT == 'core')
working-directory: ./packages/core
run: |
node -e "
const pkg = require('./package.json');
pkg.version = process.env.CORE_VER;
pkg.publishConfig = {
access: 'public',
registry: 'https://registry.npmjs.org/'
};
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
"
- name: Update React package for public npm
if: |
(env.PACKAGES_INPUT == 'both' || env.PACKAGES_INPUT == 'react')
working-directory: ./packages/react
run: |
node -e "
const pkg = require('./package.json');
pkg.version = process.env.REACT_VER;
pkg.publishConfig = {
access: 'public',
registry: 'https://registry.npmjs.org/'
};
if (pkg.dependencies && pkg.dependencies['@auth0/universal-components-core']) {
pkg.dependencies['@auth0/universal-components-core'] = process.env.CORE_VER;
}
require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
"
- name: Publish Core package to public npm
id: publish-core
if: |
(env.PACKAGES_INPUT == 'both' || env.PACKAGES_INPUT == 'core')
working-directory: ./packages/core
env:
DRY_RUN: ${{ github.event.inputs.dry_run }}
run: |
current_version=$(node -p "require('./package.json').version")
echo "Publishing @auth0/universal-components-core@$current_version with tag: $CORE_TAG"
if [ "$DRY_RUN" = "true" ]; then
echo "DRY RUN - Skipping publish"
npm publish --dry-run --tag="$CORE_TAG"
echo "published=dry-run" >> $GITHUB_OUTPUT
echo "version=$current_version" >> $GITHUB_OUTPUT
echo "Published @auth0/universal-components-core@$current_version (dry run)"
else
npm publish --provenance --access public --tag="$CORE_TAG"
echo "published=true" >> $GITHUB_OUTPUT
echo "version=$current_version" >> $GITHUB_OUTPUT
echo "Published @auth0/universal-components-core@$current_version"
fi
- name: Publish React package to public npm
id: publish-react
if: |
(env.PACKAGES_INPUT == 'both' || env.PACKAGES_INPUT == 'react')
working-directory: ./packages/react
env:
DRY_RUN: ${{ github.event.inputs.dry_run }}
run: |
current_version=$(node -p "require('./package.json').version")
echo "Publishing @auth0/universal-components-react@$current_version with tag: $REACT_TAG"
if [ "$DRY_RUN" = "true" ]; then
echo "DRY RUN - Skipping publish"
npm publish --dry-run --tag="$REACT_TAG"
echo "published=dry-run" >> $GITHUB_OUTPUT
echo "version=$current_version" >> $GITHUB_OUTPUT
else
npm publish --provenance --access public --tag="$REACT_TAG"
echo "published=true" >> $GITHUB_OUTPUT
echo "version=$current_version" >> $GITHUB_OUTPUT
echo "Published @auth0/universal-components-react@$current_version"
fi
- name: Generate Summary
env:
DRY_RUN: ${{ github.event.inputs.dry_run }}
run: |
echo "## Public NPM Publication Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$DRY_RUN" = "true" ]; then
echo "### DRY RUN MODE" >> $GITHUB_STEP_SUMMARY
echo "No packages were published. This was a test run." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo "### Published Packages" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.publish-core.outputs.published }}" != "" ]; then
echo "- \`@auth0/universal-components-core@${CORE_VER}\`" >> $GITHUB_STEP_SUMMARY
echo " - Tag: \`${CORE_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo " - Registry: https://registry.npmjs.org" >> $GITHUB_STEP_SUMMARY
echo " - Install: \`npm install @auth0/universal-components-core@${CORE_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ steps.publish-react.outputs.published }}" != "" ]; then
echo "- \`@auth0/universal-components-react@${REACT_VER}\`" >> $GITHUB_STEP_SUMMARY
echo " - Tag: \`${REACT_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo " - Registry: https://registry.npmjs.org" >> $GITHUB_STEP_SUMMARY
echo " - Install: \`npm install @auth0/universal-components-react@${REACT_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
fi
echo "### Links" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.publish-core.outputs.published }}" != "" ]; then
echo "- [View @auth0/universal-components-core on npm](https://www.npmjs.com/package/@auth0/universal-components-core)" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ steps.publish-react.outputs.published }}" != "" ]; then
echo "- [View @auth0/universal-components-react on npm](https://www.npmjs.com/package/@auth0/universal-components-react)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note:** Your internal JFrog registry continues to use \`@auth0-web-ui-components/*\` packages." >> $GITHUB_STEP_SUMMARY
- name: Create Git tags
if: github.event.inputs.dry_run != 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if [ "${{ steps.publish-core.outputs.published }}" = "true" ]; then
TAG_NAME="core@$CORE_VER"
# Check if tag exists locally and delete it
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
echo "Tag $TAG_NAME exists locally, deleting..."
git tag -d "$TAG_NAME"
fi
# Check if tag exists remotely and delete it
if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME"; then
echo "Tag $TAG_NAME exists remotely, deleting..."
git push origin ":refs/tags/$TAG_NAME" || true
fi
git tag -a "$TAG_NAME" -m "Release @auth0/universal-components-core@$CORE_VER"
git push origin "$TAG_NAME"
echo "Created tag: $TAG_NAME"
fi
if [ "${{ steps.publish-react.outputs.published }}" = "true" ]; then
TAG_NAME="react@$REACT_VER"
# Check if tag exists locally and delete it
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
echo "Tag $TAG_NAME exists locally, deleting..."
git tag -d "$TAG_NAME"
fi
# Check if tag exists remotely and delete it
if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME"; then
echo "Tag $TAG_NAME exists remotely, deleting..."
git push origin ":refs/tags/$TAG_NAME" || true
fi
git tag -a "$TAG_NAME" -m "Release @auth0/universal-components-react@$REACT_VER"
git push origin "$TAG_NAME"
echo "Created tag: $TAG_NAME"
fi
- name: Create GitHub Release for Core
if: |
github.event.inputs.dry_run != 'true' &&
steps.publish-core.outputs.published == 'true'
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2
with:
tag_name: core@${{ env.CORE_VER }}
name: "@auth0/universal-components-core@${{ env.CORE_VER }}"
body: |
Published to NPM
**Package**: `@auth0/universal-components-core`
**Version**: `${{ env.CORE_VER }}`
**NPM Tag**: `${{ env.CORE_TAG }}`
```bash
npm install @auth0/universal-components-core@${{ env.CORE_VER }}
```
[View on NPM](https://www.npmjs.com/package/@auth0/universal-components-core/v/${{ env.CORE_VER }})
prerelease: ${{ env.CORE_TAG != 'latest' }}
draft: false
- name: Create GitHub Release for React
if: |
github.event.inputs.dry_run != 'true' &&
steps.publish-react.outputs.published == 'true'
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2
with:
tag_name: react@${{ env.REACT_VER }}
name: "@auth0/universal-components-react@${{ env.REACT_VER }}"
body: |
Published to NPM
**Package**: `@auth0/universal-components-react`
**Version**: `${{ env.REACT_VER }}`
**NPM Tag**: `${{ env.REACT_TAG }}`
**Requires**: `@auth0/universal-components-core@${{ env.CORE_VER }}`
```bash
npm install @auth0/universal-components-react@${{ env.REACT_VER }}
```
[View on NPM](https://www.npmjs.com/package/@auth0/universal-components-react/v/${{ env.REACT_VER }})
prerelease: ${{ env.REACT_TAG != 'latest' }}
draft: false
- name: Restore package.json files
if: always()
run: |
git checkout packages/core/package.json packages/react/package.json || true
- name: Send Slack notification
if: |
always() &&
!cancelled() &&
github.event.inputs.dry_run != 'true' &&
github.event.inputs.skip_slack_notification != 'true'
env:
DRY_RUN: ${{ github.event.inputs.dry_run }}
SLACK_WORKFLOW_WEBHOOK: ${{ secrets.SLACK_WORKFLOW_WEBHOOK }}
run: |
echo "Preparing Slack notification..."
# Determine what was actually published
PUBLISHED_PACKAGES=""
PUBLISH_ERRORS=""
# Check core package publishing
if [ "${{ steps.publish-core.outputs.published }}" = "true" ]; then
PUBLISHED_PACKAGES="@auth0/universal-components-core@${{ steps.publish-core.outputs.version }}"
elif [ "${{ steps.publish-core.outputs.published }}" = "dry-run" ]; then
PUBLISHED_PACKAGES="@auth0/universal-components-core@${{ steps.publish-core.outputs.version }} (dry run)"
elif [ "${{ steps.publish-core.outcome }}" = "failure" ]; then
PUBLISH_ERRORS="core package failed to publish"
fi
# Check react package publishing
if [ "${{ steps.publish-react.outputs.published }}" = "true" ]; then
if [ -n "$PUBLISHED_PACKAGES" ]; then
PUBLISHED_PACKAGES="$PUBLISHED_PACKAGES, @auth0/universal-components-react@${{ steps.publish-react.outputs.version }}"
else
PUBLISHED_PACKAGES="@auth0/universal-components-react@${{ steps.publish-react.outputs.version }}"
fi
elif [ "${{ steps.publish-react.outputs.published }}" = "dry-run" ]; then
if [ -n "$PUBLISHED_PACKAGES" ]; then
PUBLISHED_PACKAGES="$PUBLISHED_PACKAGES, @auth0/universal-components-react@${{ steps.publish-react.outputs.version }} (dry run)"
else
PUBLISHED_PACKAGES="@auth0/universal-components-react@${{ steps.publish-react.outputs.version }} (dry run)"
fi
elif [ "${{ steps.publish-react.outcome }}" = "failure" ]; then
if [ -n "$PUBLISH_ERRORS" ]; then
PUBLISH_ERRORS="$PUBLISH_ERRORS, react package failed to publish"
else
PUBLISH_ERRORS="react package failed to publish"
fi
fi
# Determine overall status and message
if [ -n "$PUBLISHED_PACKAGES" ] && [ -z "$PUBLISH_ERRORS" ]; then
STATUS="SUCCESS: Packages published to public NPM"
MESSAGE="Published: $PUBLISHED_PACKAGES"
elif [ -n "$PUBLISHED_PACKAGES" ] && [ -n "$PUBLISH_ERRORS" ]; then
STATUS="WARNING: Partial publishing failure"
MESSAGE="Published: $PUBLISHED_PACKAGES. Errors: $PUBLISH_ERRORS"
elif [ -n "$PUBLISH_ERRORS" ]; then
STATUS="FAILED: Package publishing failed"
MESSAGE="Errors: $PUBLISH_ERRORS"
else
STATUS="INFO: No packages published"
MESSAGE="No packages were selected for publishing"
fi
# Get individual package versions for Slack
CORE_VERSION="Not published"
REACT_VERSION="Not published"
if [ "${{ steps.publish-core.outputs.published }}" = "true" ] || [ "${{ steps.publish-core.outputs.published }}" = "dry-run" ]; then
CORE_VERSION="${{ steps.publish-core.outputs.version }}"
fi
if [ "${{ steps.publish-react.outputs.published }}" = "true" ] || [ "${{ steps.publish-react.outputs.published }}" = "dry-run" ]; then
REACT_VERSION="${{ steps.publish-react.outputs.version }}"
fi
# Output summary for GitHub logs
echo "=== WORKFLOW SUMMARY ==="
echo "Status: $STATUS"
echo "Message: $MESSAGE"
echo ""
# Send notification to Slack (if webhook is configured)
if [ -n "$SLACK_WORKFLOW_WEBHOOK" ]; then
echo "Sending Slack notification..."
HTTP_CODE=$(curl -X POST "$SLACK_WORKFLOW_WEBHOOK" \
-H "Content-Type: application/json" \
-w "%{http_code}" \
-o /tmp/slack_response.txt \
-d "{
\"status\": \"$STATUS\",
\"message\": \"$MESSAGE\",
\"package_details\": \"${PUBLISHED_PACKAGES:-'No packages published'}\",
\"core_version\": \"$CORE_VERSION\",
\"react_version\": \"$REACT_VERSION\",
\"registry\": \"https://registry.npmjs.org\",
\"trigger\": \"${{ github.event_name }}\",
\"actor\": \"${{ github.actor }}\",
\"action_url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}")
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "Slack notification sent successfully (HTTP $HTTP_CODE)"
else
echo "WARNING: Slack notification failed (HTTP $HTTP_CODE)"
cat /tmp/slack_response.txt
fi
else
echo "Slack webhook not configured, skipping notification"
fi
- name: Handle workflow failure
if: failure()
run: |
echo "## Workflow Failed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The workflow encountered an error. Please check the logs above for details." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Common Issues" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Authentication**: Verify NPM_TOKEN secret is configured correctly" >> $GITHUB_STEP_SUMMARY
echo "- **Version conflict**: Package version may already exist on npm" >> $GITHUB_STEP_SUMMARY
echo "- **Build failure**: Check if packages build successfully" >> $GITHUB_STEP_SUMMARY
echo "- **Network issues**: NPM registry may be unreachable" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "1. Review the error logs in this workflow run" >> $GITHUB_STEP_SUMMARY
echo "2. Fix the issue and retry the workflow" >> $GITHUB_STEP_SUMMARY
echo "3. If needed, manually publish using: \`pnpm publish\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY