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
68 changes: 68 additions & 0 deletions .github/workflows/ghr_backfill.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Backfill GitHub Package Registry

# Manually triggered. Copies versions already published on npm into the GitHub
# Package Registry so GHR mirrors npm. Re-publishing the npm tarball guarantees
# the GHR copy is byte-identical to what npm consumers received (no rebuild).
#
# Idempotent: scripts/publish.sh skips any version already present on GHR, so
# this can be re-run safely.

on:
workflow_dispatch:
inputs:
version:
description: "Single version to backfill (e.g. 4.0.0). Leave blank to process all published npm versions (versions already on GHR are skipped)."
required: false
default: ""
dry_run:
description: "Dry run: report what would be published to GHR without publishing."
type: boolean
required: false
default: false

jobs:
backfill:
name: Backfill npm versions to GHR
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout branch
uses: actions/checkout@v4
Comment thread
arnica-github-connector[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 18

- name: Configure GHR auth
run: echo "//npm.pkg.github.com/:_authToken=\${NODE_AUTH_TOKEN}" >> ~/.npmrc

- name: Backfill
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRY_RUN: ${{ inputs.dry_run }}
INPUT_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
pkg="@optimizely/react-sdk"
if [[ -n "$INPUT_VERSION" ]]; then
versions="$INPUT_VERSION"
else
versions=$(npm view "$pkg" versions --json --registry https://registry.npmjs.org | jq -r '.[]')
fi
for v in $versions; do
echo "::group::${pkg}@${v}"
# npm pack writes the tarball filename to stdout and notices/errors to
# stderr; keep stderr visible so pack failures (auth/404/network) show
# in the logs. pipefail (set above) makes a failed pack abort the job.
tarball=$(npm pack "${pkg}@${v}" --registry https://registry.npmjs.org | tail -n1)
scripts/publish.sh "https://npm.pkg.github.com" "$tarball"
rm -f "$tarball"
echo "::endgroup::"
done
Comment thread
arnica-github-connector[bot] marked this conversation as resolved.
47 changes: 27 additions & 20 deletions .github/workflows/react_release.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
name: Publish React SDK to NPM
name: Publish React SDK

on:
release:
types: [ published ]

jobs:
publish:
name: Publish to NPM
name: Publish to NPM and GitHub Package Registry
runs-on: ubuntu-latest
Comment thread
junaed-optimizely marked this conversation as resolved.
permissions:
contents: read
packages: write
steps:
- name: Checkout branch
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Node
uses: actions/setup-node@v4
Expand All @@ -19,27 +24,29 @@ jobs:
registry-url: "https://registry.npmjs.org/"
always-auth: "true"
cache: 'npm'
env:
NODE_AUTH_TOKEN: ${{ secrets.PUBLISH_REACT_TO_NPM_FROM_GITHUB }}

- name: Configure GHR auth
run: echo "//npm.pkg.github.com/:_authToken=\${NODE_AUTH_TOKEN}" >> ~/.npmrc

- name: Install dependencies
run: npm ci
run: npm ci --ignore-scripts

- id: npm-tag
name: Determine NPM tag
- name: Test, build, and pack
id: pack
run: |
version=$(jq -r '.version' package.json)
if [[ "$version" == *"-beta"* ]]; then
echo "npm-tag=beta" >> "$GITHUB_OUTPUT"
elif [[ "$version" == *"-alpha"* ]]; then
echo "npm-tag=alpha" >> "$GITHUB_OUTPUT"
elif [[ "$version" == *"-rc"* ]]; then
echo "npm-tag=rc" >> "$GITHUB_OUTPUT"
else
echo "npm-tag=latest" >> "$GITHUB_OUTPUT"
fi

- name: Test, build, then publish
npm run test
npm run build
tarball=$(npm pack --ignore-scripts | tail -n1)
echo "tarball=$tarball" >> "$GITHUB_OUTPUT"

- name: Publish to NPM
env:
NODE_AUTH_TOKEN: ${{ secrets.PUBLISH_REACT_TO_NPM_FROM_GITHUB }}
run: npm publish --tag ${{ steps.npm-tag.outputs['npm-tag'] }}
TARBALL: ${{ steps.pack.outputs.tarball }}
run: scripts/publish.sh "https://registry.npmjs.org" "$TARBALL"

- name: Publish to GitHub Package Registry
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TARBALL: ${{ steps.pack.outputs.tarball }}
run: scripts/publish.sh "https://npm.pkg.github.com" "$TARBALL"
89 changes: 89 additions & 0 deletions scripts/publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
#
# Registry-agnostic, idempotent publish for @optimizely/react-sdk.
#
# Usage:
# scripts/publish.sh <registry-url> [tarball]
#
# Args:
# registry-url Target registry, e.g. https://registry.npmjs.org
# or https://npm.pkg.github.com
# tarball Optional. When given, publishes that packed tarball instead
# of the current working directory (used by the GHR backfill).
#
# Env:
# NODE_AUTH_TOKEN Auth token for the target registry. The caller must have an
# .npmrc entry supplying auth for <registry-url>'s host, e.g.
# `//<host>/:_authToken=${NODE_AUTH_TOKEN}` (setup-node writes
# this). This script passes --registry explicitly, so no
# scope-to-registry routing is required (and the GHR job
# intentionally does NOT route @optimizely to GHR, so that
# `npm ci` still installs dependencies from npm).
# DRY_RUN When "true", report what would happen (publish vs. skip)
# without actually publishing.
#
# Behavior:
# - Skips (exit 0) if the version already exists on the target registry, so
# re-running a release or the backfill is always a safe no-op.
# - Computes the dist-tag from the version string (beta/alpha/rc/latest) so a
# pre-release never moves the `latest` pointer.
# - For a stable release whose major is older than the registry's current
# `latest` (e.g. a 5.x patch shipped after 6.x), tags it `v<major>-latest`
# instead of `latest`, so `latest` never moves backwards onto an old major.
set -euo pipefail

dry_run="${DRY_RUN:-false}"

registry="${1:?usage: publish.sh <registry-url> [tarball]}"
tarball="${2:-}"

if [[ -n "$tarball" ]]; then
# Derive name/version from the tarball's own package.json so the guard matches
# exactly what we're about to publish (backfill of historical versions).
meta=$(tar -xzO -f "$tarball" package/package.json)
pkg=$(printf '%s' "$meta" | jq -r '.name')
version=$(printf '%s' "$meta" | jq -r '.version')
else
pkg=$(jq -r '.name' package.json)
version=$(jq -r '.version' package.json)
fi

case "$version" in
*-beta*) tag=beta ;;
*-alpha*) tag=alpha ;;
*-rc*) tag=rc ;;
*) tag=latest ;;
esac

if npm view "${pkg}@${version}" version --registry "$registry" >/dev/null 2>&1; then
echo "Version ${pkg}@${version} already on ${registry}, skipping."
exit 0
fi

# Don't let a stable release move `latest` backwards onto an older major. If the
# registry's current `latest` is already a newer major than this version, publish
# under `v<major>-latest` instead. (Only reached when actually publishing, so the
# extra lookup is skipped for no-op re-runs above.)
if [[ "$tag" == "latest" ]]; then
current_latest=$(npm view "${pkg}" version --registry "$registry" 2>/dev/null || true)
our_major=${version%%.*}
latest_major=${current_latest%%.*}
if [[ "$our_major" =~ ^[0-9]+$ && "$latest_major" =~ ^[0-9]+$ ]] && (( our_major < latest_major )); then
tag="v${our_major}-latest"
echo "Current latest is ${current_latest} (major ${latest_major}); tagging ${version} as ${tag} to preserve latest."
fi
fi

if [[ "$dry_run" == "true" ]]; then
echo "[dry-run] would publish ${pkg}@${version} (tag: ${tag}) to ${registry}"
exit 0
fi

echo "Publishing ${pkg}@${version} (tag: ${tag}) to ${registry}"
if [[ -n "$tarball" ]]; then
# The tarball is a prebuilt artifact; skip lifecycle scripts (prepublishOnly
# = test + build) that would otherwise run from the current package.json.
npm publish "$tarball" --registry "$registry" --tag "$tag" --ignore-scripts
else
npm publish --registry "$registry" --tag "$tag"
fi
Loading