From a73db2d6b1cd4dc8ab8dca29cf95057585047ebc Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:20:06 +0600 Subject: [PATCH 1/3] [FSSDK-12882] release pipeline update --- .github/workflows/ghr_backfill.yml | 67 ++++++++++++++++++++++++++++ .github/workflows/react_release.yml | 58 ++++++++++++++++-------- scripts/publish.sh | 68 +++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/ghr_backfill.yml create mode 100755 scripts/publish.sh diff --git a/.github/workflows/ghr_backfill.yml b/.github/workflows/ghr_backfill.yml new file mode 100644 index 00000000..c49c2754 --- /dev/null +++ b/.github/workflows/ghr_backfill.yml @@ -0,0 +1,67 @@ +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 backfill all missing versions." + 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 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 18 + registry-url: "https://registry.npmjs.org/" + always-auth: "true" + + # Add GHR auth for publishing. npm expands ${NODE_AUTH_TOKEN} at runtime, and + # scripts/publish.sh passes --registry explicitly, so the default registry + # stays on npm (backfill reads tarballs from npm, writes them to GHR). + - 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: ${{ github.event.inputs.dry_run }} + run: | + set -euo pipefail + pkg="@optimizely/react-sdk" + + if [[ -n "${{ github.event.inputs.version }}" ]]; then + versions="${{ github.event.inputs.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}" + tarball=$(npm pack "${pkg}@${v}" --registry https://registry.npmjs.org 2>/dev/null | tail -n1) + scripts/publish.sh "https://npm.pkg.github.com" "$tarball" + rm -f "$tarball" + echo "::endgroup::" + done diff --git a/.github/workflows/react_release.yml b/.github/workflows/react_release.yml index 2b02c466..6500a232 100644 --- a/.github/workflows/react_release.yml +++ b/.github/workflows/react_release.yml @@ -1,11 +1,11 @@ -name: Publish React SDK to NPM +name: Publish React SDK on: release: types: [ published ] jobs: - publish: + publish-npm: name: Publish to NPM runs-on: ubuntu-latest steps: @@ -19,27 +19,47 @@ jobs: registry-url: "https://registry.npmjs.org/" always-auth: "true" cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Publish to NPM env: NODE_AUTH_TOKEN: ${{ secrets.PUBLISH_REACT_TO_NPM_FROM_GITHUB }} + run: scripts/publish.sh "https://registry.npmjs.org" + + publish-ghr: + name: Publish to GitHub Package Registry + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + # Install from the public npm registry so scoped dependencies + # (e.g. @optimizely/optimizely-sdk) resolve normally. We do NOT route the + # @optimizely scope to GHR here, otherwise `npm ci` would look for + # dependencies on GHR instead of npm. + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 18 + registry-url: "https://registry.npmjs.org/" + always-auth: "true" + cache: 'npm' - name: Install dependencies run: npm ci - - id: npm-tag - name: Determine NPM tag - 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 + # Add GHR auth for the publish only. npm expands ${NODE_AUTH_TOKEN} at + # runtime, and scripts/publish.sh passes --registry explicitly, so the + # default registry stays on npm. + - name: Configure GHR auth + run: echo "//npm.pkg.github.com/:_authToken=\${NODE_AUTH_TOKEN}" >> ~/.npmrc + + - name: Publish to GHR env: - NODE_AUTH_TOKEN: ${{ secrets.PUBLISH_REACT_TO_NPM_FROM_GITHUB }} - run: npm publish --tag ${{ steps.npm-tag.outputs['npm-tag'] }} + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: scripts/publish.sh "https://npm.pkg.github.com" diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100755 index 00000000..ab73d2e3 --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# +# Registry-agnostic, idempotent publish for @optimizely/react-sdk. +# +# Usage: +# scripts/publish.sh [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 is expected +# to have configured .npmrc (e.g. via actions/setup-node) so +# that @optimizely resolves to with this token. +# 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. +set -euo pipefail + +dry_run="${DRY_RUN:-false}" + +registry="${1:?usage: publish.sh [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 + +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 From 3a6bbab28da6d7c920ae009d5705dd35f420f04f Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:51:15 +0600 Subject: [PATCH 2/3] [FSSDK-12882] review feedback --- .github/workflows/ghr_backfill.yml | 7 +++++-- .github/workflows/react_release.yml | 2 ++ scripts/publish.sh | 10 +++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ghr_backfill.yml b/.github/workflows/ghr_backfill.yml index c49c2754..a3f0bdf7 100644 --- a/.github/workflows/ghr_backfill.yml +++ b/.github/workflows/ghr_backfill.yml @@ -30,6 +30,8 @@ jobs: steps: - name: Checkout branch uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up Node uses: actions/setup-node@v4 @@ -48,12 +50,13 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} DRY_RUN: ${{ github.event.inputs.dry_run }} + INPUT_VERSION: ${{ github.event.inputs.version }} run: | set -euo pipefail pkg="@optimizely/react-sdk" - if [[ -n "${{ github.event.inputs.version }}" ]]; then - versions="${{ github.event.inputs.version }}" + if [[ -n "$INPUT_VERSION" ]]; then + versions="$INPUT_VERSION" else versions=$(npm view "$pkg" versions --json --registry https://registry.npmjs.org | jq -r '.[]') fi diff --git a/.github/workflows/react_release.yml b/.github/workflows/react_release.yml index 6500a232..a3efb544 100644 --- a/.github/workflows/react_release.yml +++ b/.github/workflows/react_release.yml @@ -11,6 +11,8 @@ jobs: steps: - name: Checkout branch uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up Node uses: actions/setup-node@v4 diff --git a/scripts/publish.sh b/scripts/publish.sh index ab73d2e3..b8865a4c 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -12,9 +12,13 @@ # of the current working directory (used by the GHR backfill). # # Env: -# NODE_AUTH_TOKEN Auth token for the target registry. The caller is expected -# to have configured .npmrc (e.g. via actions/setup-node) so -# that @optimizely resolves to with this token. +# NODE_AUTH_TOKEN Auth token for the target registry. The caller must have an +# .npmrc entry supplying auth for 's host, e.g. +# `///:_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. # From 9b437e54dc3cdb65ead4fae9346cb390e298f790 Mon Sep 17 00:00:00 2001 From: Md Junaed Hossain <169046794+junaed-optimizely@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:52:15 +0600 Subject: [PATCH 3/3] [FSSDK-12882] review feedback --- .github/workflows/ghr_backfill.yml | 5 ++++- .github/workflows/react_release.yml | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ghr_backfill.yml b/.github/workflows/ghr_backfill.yml index a3f0bdf7..18f07f04 100644 --- a/.github/workflows/ghr_backfill.yml +++ b/.github/workflows/ghr_backfill.yml @@ -63,7 +63,10 @@ jobs: for v in $versions; do echo "::group::${pkg}@${v}" - tarball=$(npm pack "${pkg}@${v}" --registry https://registry.npmjs.org 2>/dev/null | tail -n1) + # 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::" diff --git a/.github/workflows/react_release.yml b/.github/workflows/react_release.yml index a3efb544..e33a45ed 100644 --- a/.github/workflows/react_release.yml +++ b/.github/workflows/react_release.yml @@ -39,6 +39,8 @@ jobs: steps: - name: Checkout branch uses: actions/checkout@v4 + with: + persist-credentials: false # Install from the public npm registry so scoped dependencies # (e.g. @optimizely/optimizely-sdk) resolve normally. We do NOT route the