Skip to content

Commit ba37dcd

Browse files
[FSSDK-12881] impl
1 parent 7d8e5ba commit ba37dcd

3 files changed

Lines changed: 186 additions & 14 deletions

File tree

.github/workflows/ghr_backfill.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Backfill GitHub Package Registry
2+
3+
# Manually triggered. Copies versions already published on npm into the GitHub
4+
# Package Registry (GPR) so GPR mirrors npm. Re-publishing the npm tarball
5+
# guarantees the GPR copy is byte-identical to what npm consumers received
6+
# (no rebuild).
7+
#
8+
# dist-tags: each backfilled version is published under the dist-tag npm
9+
# currently assigns it (latest / v{major}-latest / beta / alpha / rc), so GPR
10+
# mirrors npm's pointers. Versions npm no longer tags are published under an
11+
# "imported" tag, which never disturbs `latest`.
12+
#
13+
# Idempotent: scripts/publish.sh skips any version already present on GPR, so
14+
# this can be re-run safely.
15+
16+
on:
17+
workflow_dispatch:
18+
inputs:
19+
version:
20+
description: "Single version to backfill (e.g. 5.3.4). Leave blank to backfill all versions."
21+
required: false
22+
default: ""
23+
dry_run:
24+
description: "Dry run: report what would be published to GPR without publishing."
25+
type: boolean
26+
required: false
27+
default: false
28+
29+
jobs:
30+
backfill:
31+
name: Backfill npm versions to GPR
32+
runs-on: ubuntu-latest
33+
permissions:
34+
contents: read
35+
packages: write
36+
steps:
37+
- name: Checkout branch
38+
uses: actions/checkout@v4
39+
40+
- name: Set up Node
41+
uses: actions/setup-node@v4
42+
with:
43+
node-version: 18
44+
registry-url: "https://registry.npmjs.org/"
45+
always-auth: "true"
46+
47+
# Add GPR auth for publishing. npm expands ${NODE_AUTH_TOKEN} at runtime,
48+
# and scripts/publish.sh passes --registry explicitly, so the default
49+
# registry stays on npm (backfill reads tarballs from npm, writes to GPR).
50+
- name: Configure GitHub Package Registry auth
51+
run: echo "//npm.pkg.github.com/:_authToken=\${NODE_AUTH_TOKEN}" >> ~/.npmrc
52+
env:
53+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54+
55+
- name: Backfill
56+
env:
57+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58+
DRY_RUN: ${{ github.event.inputs.dry_run }}
59+
INPUT_VERSION: ${{ github.event.inputs.version }}
60+
run: |
61+
set -euo pipefail
62+
pkg="@optimizely/optimizely-sdk"
63+
npm_registry="https://registry.npmjs.org"
64+
gpr_registry="https://npm.pkg.github.com"
65+
66+
if [[ -n "$INPUT_VERSION" ]]; then
67+
versions="$INPUT_VERSION"
68+
else
69+
versions=$(npm view "$pkg" versions --json --registry "$npm_registry" | jq -r '.[]')
70+
fi
71+
72+
# Read npm's current dist-tags once and build a version -> tag map, so
73+
# each backfilled version lands under the same tag it has on npm.
74+
# `npm dist-tag ls` prints "tag: version" lines. Anything not currently
75+
# tagged falls back to "imported" (a neutral tag that never moves latest).
76+
dist_tags_json=$(npm dist-tag ls "$pkg" --registry "$npm_registry" \
77+
| jq -R -s 'split("\n") | map(select(length > 0) | split(": ")) | map({(.[1]): .[0]}) | add // {}')
78+
79+
for v in $versions; do
80+
echo "::group::${pkg}@${v}"
81+
tag=$(printf '%s' "$dist_tags_json" | jq -r --arg v "$v" '.[$v] // "imported"')
82+
# npm pack writes the tarball filename to stdout and notices/errors to
83+
# stderr; keep stderr visible so pack failures (auth/404/network) show
84+
# in the logs. pipefail (set above) makes a failed pack abort the job.
85+
tarball=$(npm pack "${pkg}@${v}" --registry "$npm_registry" | tail -n1)
86+
scripts/publish.sh "$gpr_registry" "$tarball" "$tag"
87+
rm -f "$tarball"
88+
echo "::endgroup::"
89+
done

.github/workflows/release.yml

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Publish SDK to NPM
1+
name: Publish SDK to NPM and GitHub Package Registry
22

33
on:
44
release:
@@ -7,9 +7,12 @@ on:
77

88
jobs:
99
publish:
10-
name: Publish to NPM
10+
name: Publish to NPM and GitHub Package Registry
1111
runs-on: ubuntu-latest
1212
if: ${{ github.event_name == 'workflow_dispatch' || !github.event.release.draft }}
13+
permissions:
14+
contents: read
15+
packages: write
1316
steps:
1417
- name: Checkout branch
1518
uses: actions/checkout@v4
@@ -20,11 +23,19 @@ jobs:
2023
node-version: 18
2124
registry-url: "https://registry.npmjs.org/"
2225
always-auth: "true"
23-
env:
24-
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
2526

27+
# Add auth for the GitHub Package Registry publish. npm expands
28+
# ${NODE_AUTH_TOKEN} at runtime per-step, so each publish step uses the
29+
# right token for its registry. We do NOT route the @optimizely scope to
30+
# GPR, so `npm ci` still resolves dependencies from npm; publish.sh passes
31+
# --registry explicitly to target GPR.
32+
- name: Configure GitHub Package Registry auth
33+
run: echo "//npm.pkg.github.com/:_authToken=\${NODE_AUTH_TOKEN}" >> ~/.npmrc
34+
35+
# Deps only. --ignore-scripts stops `prepare` from building here; we build
36+
# exactly once in the pack step below.
2637
- name: Install dependencies
27-
run: npm install
38+
run: npm ci --ignore-scripts
2839

2940
- id: latest-release
3041
name: Export latest release git tag
@@ -42,7 +53,7 @@ jobs:
4253
run: |
4354
VERSION=$(jq -r '.version' package.json)
4455
LATEST_RELEASE_TAG="${{ steps.latest-release.outputs['latest-release-tag']}}"
45-
56+
4657
if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then
4758
RELEASE_TAG=${GITHUB_REF#refs/tags/}
4859
else
@@ -61,17 +72,29 @@ jobs:
6172
echo "npm-tag=v$(echo $VERSION | awk -F. '{print $1}')-latest" >> "$GITHUB_OUTPUT"
6273
fi
6374
75+
# Test, build, and pack a single tarball. Both registries publish this
76+
# same artifact, so npm and GPR receive byte-identical bytes and the test
77+
# suite runs only once. --ignore-scripts on pack avoids a rebuild.
78+
- id: pack
79+
name: Test, build, and pack
80+
run: |
81+
npm test
82+
npm run build
83+
tarball=$(npm pack --ignore-scripts | tail -n1)
84+
echo "tarball=$tarball" >> "$GITHUB_OUTPUT"
85+
6486
- id: release
65-
name: Test, build and publish to npm
87+
name: Publish to NPM
6688
env:
67-
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
68-
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
6989
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
70-
run: |
71-
if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then
72-
DRY_RUN="--dry-run"
73-
fi
74-
npm publish --tag=${{ steps.npm-tag.outputs['npm-tag'] }} $DRY_RUN
90+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' }}
91+
run: scripts/publish.sh "https://registry.npmjs.org" "${{ steps.pack.outputs.tarball }}" "${{ steps.npm-tag.outputs['npm-tag'] }}"
92+
93+
- name: Publish to GitHub Package Registry
94+
env:
95+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
96+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' }}
97+
run: scripts/publish.sh "https://npm.pkg.github.com" "${{ steps.pack.outputs.tarball }}" "${{ steps.npm-tag.outputs['npm-tag'] }}"
7598

7699
# - name: Report results to Jellyfish
77100
# uses: optimizely/jellyfish-deployment-reporter-action@main

scripts/publish.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Registry-agnostic, idempotent publish for @optimizely/optimizely-sdk.
4+
#
5+
# Usage:
6+
# scripts/publish.sh <registry-url> <tarball> <dist-tag>
7+
#
8+
# Args:
9+
# registry-url Target registry, e.g. https://registry.npmjs.org
10+
# or https://npm.pkg.github.com
11+
# tarball Prebuilt package tarball (output of `npm pack`) to publish.
12+
# The release flow packs it once and publishes the same bytes to
13+
# both registries; the backfill packs each version from npm.
14+
# dist-tag npm dist-tag to publish under. The caller decides the tag; this
15+
# script is deliberately tag-agnostic so the JS SDK keeps its
16+
# existing tag logic in the workflow (latest / v{major}-latest /
17+
# beta / alpha / rc).
18+
#
19+
# Env:
20+
# NODE_AUTH_TOKEN Auth token for the target registry. An .npmrc entry must
21+
# reference it for <registry-url>'s host, e.g.
22+
# `//<host>/:_authToken=${NODE_AUTH_TOKEN}` (setup-node writes
23+
# this for npm; the workflow adds one for GitHub Package
24+
# Registry). We pass --registry explicitly, so no
25+
# scope-to-registry routing is needed (and the @optimizely
26+
# scope is intentionally NOT routed to GPR, so `npm ci` still
27+
# installs dependencies from npm).
28+
# DRY_RUN When "true", report the action (publish vs. skip) without
29+
# actually publishing.
30+
#
31+
# Behavior:
32+
# - Skips (exit 0) if the version already exists on the target registry, so
33+
# re-running a release or the backfill is always a safe no-op.
34+
# - Publishes the prebuilt tarball with --ignore-scripts so lifecycle scripts
35+
# (prepublishOnly = test, prepare = build) don't re-run from package.json.
36+
set -euo pipefail
37+
38+
registry="${1:?usage: publish.sh <registry-url> <tarball> <dist-tag>}"
39+
tarball="${2:?usage: publish.sh <registry-url> <tarball> <dist-tag>}"
40+
tag="${3:?usage: publish.sh <registry-url> <tarball> <dist-tag>}"
41+
dry_run="${DRY_RUN:-false}"
42+
43+
# Derive name/version from the tarball's own package.json so the existence guard
44+
# matches exactly what we're about to publish.
45+
meta=$(tar -xzO -f "$tarball" package/package.json)
46+
pkg=$(printf '%s' "$meta" | jq -r '.name')
47+
version=$(printf '%s' "$meta" | jq -r '.version')
48+
49+
if npm view "${pkg}@${version}" version --registry "$registry" >/dev/null 2>&1; then
50+
echo "Version ${pkg}@${version} already on ${registry}, skipping."
51+
exit 0
52+
fi
53+
54+
if [[ "$dry_run" == "true" ]]; then
55+
echo "[dry-run] would publish ${pkg}@${version} (tag: ${tag}) to ${registry}"
56+
exit 0
57+
fi
58+
59+
echo "Publishing ${pkg}@${version} (tag: ${tag}) to ${registry}"
60+
npm publish "$tarball" --registry "$registry" --tag "$tag" --ignore-scripts

0 commit comments

Comments
 (0)