From 7bdca1600e785f58feb4e874c11acfc0b152a522 Mon Sep 17 00:00:00 2001 From: Brian Herbert Date: Wed, 12 Aug 2026 11:33:06 -0500 Subject: [PATCH 1/7] feat: publish UI stylesheet to CDN on release (YPE-1733) When a new @youversion/platform-react-ui version is published, upload packages/ui/dist/tailwind.css to the cdn.youversion.com origin bucket so it is served at https://cdn.youversion.com/platform//bible.css. Auth uses Workload Identity Federation (no static keys): only this repo can impersonate platform-sdk-cdn-publisher@yvplatform-prod, which can only write under the bucket's platform/ prefix. The path segment lives in packages/ui/CDN_CSS_MAJOR_VERSION and is bumped only for breaking CSS changes (documented in PUBLISHING.md). Co-authored-by: Cursor --- .github/workflows/release.yml | 29 +++++++++++++++++++++++ PUBLISHING.md | 39 +++++++++++++++++++++++++++++++ packages/ui/CDN_CSS_MAJOR_VERSION | 1 + 3 files changed, 69 insertions(+) create mode 100644 packages/ui/CDN_CSS_MAJOR_VERSION diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0a29fefc..d8bf8cec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,3 +67,32 @@ jobs: echo "The following packages were published to NPM:" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -r '.[] | "- \(.name)@\(.version)"' >> $GITHUB_STEP_SUMMARY + + # When a new @youversion/platform-react-ui version is published, upload its + # compiled stylesheet to the cdn.youversion.com origin bucket so it is served + # at https://cdn.youversion.com/platform//bible.css (YPE-1733). + # The path segment comes from packages/ui/CDN_CSS_MAJOR_VERSION and is + # bumped only for breaking CSS changes — see PUBLISHING.md. + - name: Authenticate to Google Cloud + if: steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui') + uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 + with: + workload_identity_provider: ${{ secrets.WIF_PROVIDER }} + service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }} + + - name: Set up Cloud SDK + if: steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui') + uses: google-github-actions/setup-gcloud@e427ad8a34f8676edf47cf7d7925499adf3eb74f # v2 + + - name: Publish UI stylesheet to CDN + if: steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui') + run: | + CSS_MAJOR_VERSION="$(tr -d '[:space:]' < packages/ui/CDN_CSS_MAJOR_VERSION)" + test -s packages/ui/dist/tailwind.css + gcloud storage cp packages/ui/dist/tailwind.css \ + "gs://cdn-yv-platform-prod/platform/${CSS_MAJOR_VERSION}/bible.css" \ + --cache-control="public, max-age=300, must-revalidate" \ + --content-type="text/css" + echo "## CDN Stylesheet :art:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Uploaded \`packages/ui/dist/tailwind.css\` to https://cdn.youversion.com/platform/${CSS_MAJOR_VERSION}/bible.css" >> $GITHUB_STEP_SUMMARY diff --git a/PUBLISHING.md b/PUBLISHING.md index 83d1abd0..785e2ec9 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -59,6 +59,45 @@ Required packages: The workflow keeps `NPM_TOKEN` as a fallback for any package where Trusted Publishing isn't configured yet. If you set one, generate it as an **Automation token** — not a Publish or personal-user token. Automation tokens explicitly bypass npm's 2FA-on-publish, which CI cannot satisfy. A Publish token will fail every publish with `EOTP` / "need a one-time password" (see [`RELEASE-RUNBOOK.md` §5](./RELEASE-RUNBOOK.md#5-otp--2fa-error-class-wrong-token-type)). Remove `NPM_TOKEN` once all three packages are on Trusted Publishing. +## CDN Stylesheet (bible.css) + +When the Release workflow publishes a new `@youversion/platform-react-ui` version, it also uploads the package's compiled stylesheet (`packages/ui/dist/tailwind.css`) to the YouVersion static-asset CDN, where it is served at: + +``` +https://cdn.youversion.com/platform//bible.css +``` + +This lets non-bundler consumers (e.g. server-rendered pages) link a stable stylesheet URL instead of extracting CSS from the npm package. + +### The CSS major version constant + +The `` path segment is defined in **one place**: the `packages/ui/CDN_CSS_MAJOR_VERSION` file (currently `1`). + +- **Do not bump it for routine releases.** The file at `/platform//bible.css` is overwritten in place with each UI package release. +- **Bump it only when the CSS changes in a breaking way** (selectors/variables/class names that existing consumers depend on are removed or behave differently). Bumping starts publishing to a new `/platform//bible.css` URL and leaves the old file untouched for existing consumers. + +### How the upload authenticates + +The workflow uses Workload Identity Federation (OIDC) — no static GCP keys, matching how this repo publishes to npm via trusted publishing: + +1. `google-github-actions/auth` exchanges the GitHub Actions OIDC token through the `github-actions-pool` Workload Identity Pool in the `yvplatform-prod` GCP project. +2. Only this repository (`youversion/platform-sdk-react`) may impersonate the dedicated service account `platform-sdk-cdn-publisher@yvplatform-prod.iam.gserviceaccount.com`. +3. That service account can only write objects under the `platform/` prefix of the `cdn-yv-platform-prod` origin bucket (IAM condition), nothing else. + +Repository Actions secrets: + +- `WIF_PROVIDER` — full resource name of the Workload Identity Pool provider +- `WIF_SERVICE_ACCOUNT` — `platform-sdk-cdn-publisher@yvplatform-prod.iam.gserviceaccount.com` + +### Caching + +`bible.css` is a mutable object at a stable URL, so it is uploaded with `Cache-Control: public, max-age=300, must-revalidate`. Consumers pick up new releases within ~5 minutes. + +### Troubleshooting the CDN upload + +- The upload steps only run when `@youversion/platform-react-ui` is among the published packages. +- npm publishing happens before the CDN upload; a failed upload does not affect the npm release. Fix the issue and re-run the failed job, or upload manually with `gcloud storage cp` if you have write access to the bucket. + ## Troubleshooting ### "Version Packages" PR Not Created diff --git a/packages/ui/CDN_CSS_MAJOR_VERSION b/packages/ui/CDN_CSS_MAJOR_VERSION new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/packages/ui/CDN_CSS_MAJOR_VERSION @@ -0,0 +1 @@ +1 From 753b3f3390533e7493123d28505476b14eb91b54 Mon Sep 17 00:00:00 2001 From: Brian Herbert Date: Wed, 12 Aug 2026 11:38:37 -0500 Subject: [PATCH 2/7] chore: add empty changeset (CI-only change, no release) Co-authored-by: Cursor --- .changeset/cdn-css-upload-ci.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/cdn-css-upload-ci.md diff --git a/.changeset/cdn-css-upload-ci.md b/.changeset/cdn-css-upload-ci.md new file mode 100644 index 00000000..a845151c --- /dev/null +++ b/.changeset/cdn-css-upload-ci.md @@ -0,0 +1,2 @@ +--- +--- From c208323dddc3cc63aa8bda2497a6b81cc6b756b6 Mon Sep 17 00:00:00 2001 From: Brian Herbert Date: Wed, 12 Aug 2026 13:13:41 -0500 Subject: [PATCH 3/7] fix: allow manual CDN re-upload when a release's CDN step fails Re-running a failed Release job cannot retry the CDN upload because changesets reports nothing newly published on the re-run. Add a workflow_dispatch trigger that skips versioning/publishing and always uploads the freshly built stylesheet (idempotent), and document it as the recovery path. Addresses Greptile P1 review feedback. Co-authored-by: Cursor --- .github/workflows/release.yml | 16 +++++++++++++--- PUBLISHING.md | 5 +++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8bf8cec..587488c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,12 @@ on: push: branches: - main + # Manual recovery path: if the CDN stylesheet upload fails after packages were + # already published to npm, a job re-run cannot retry it (changesets reports + # nothing newly published on the re-run). Instead, run this workflow manually + # from main: the changesets step is skipped and the CDN upload always runs, + # publishing the freshly built stylesheet. The upload is idempotent. + workflow_dispatch: concurrency: ${{ github.workflow }}-${{ github.ref }} @@ -45,6 +51,9 @@ jobs: run: pnpm build - name: Create Release Pull Request or Publish to NPM + # Skipped on workflow_dispatch (manual CDN re-upload) so a manual run can + # never version or publish packages as a side effect. + if: github.event_name == 'push' id: changesets uses: changesets/action@v1 with: @@ -73,19 +82,20 @@ jobs: # at https://cdn.youversion.com/platform//bible.css (YPE-1733). # The path segment comes from packages/ui/CDN_CSS_MAJOR_VERSION and is # bumped only for breaking CSS changes — see PUBLISHING.md. + # Also runs on workflow_dispatch as the manual recovery path (see `on:` above). - name: Authenticate to Google Cloud - if: steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui') + if: github.event_name == 'workflow_dispatch' || (steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui')) uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2 with: workload_identity_provider: ${{ secrets.WIF_PROVIDER }} service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }} - name: Set up Cloud SDK - if: steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui') + if: github.event_name == 'workflow_dispatch' || (steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui')) uses: google-github-actions/setup-gcloud@e427ad8a34f8676edf47cf7d7925499adf3eb74f # v2 - name: Publish UI stylesheet to CDN - if: steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui') + if: github.event_name == 'workflow_dispatch' || (steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui')) run: | CSS_MAJOR_VERSION="$(tr -d '[:space:]' < packages/ui/CDN_CSS_MAJOR_VERSION)" test -s packages/ui/dist/tailwind.css diff --git a/PUBLISHING.md b/PUBLISHING.md index 785e2ec9..3aad9427 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -95,8 +95,9 @@ Repository Actions secrets: ### Troubleshooting the CDN upload -- The upload steps only run when `@youversion/platform-react-ui` is among the published packages. -- npm publishing happens before the CDN upload; a failed upload does not affect the npm release. Fix the issue and re-run the failed job, or upload manually with `gcloud storage cp` if you have write access to the bucket. +- On `main` pushes, the upload steps only run when `@youversion/platform-react-ui` is among the published packages. +- npm publishing happens before the CDN upload; a failed upload does not affect the npm release. +- **Do not retry by re-running the failed job**: on a re-run, changesets reports nothing newly published, so the CDN steps are skipped. Instead, after fixing the issue, trigger the **Release workflow manually** (Actions → Release → "Run workflow" on `main`). Manual runs skip versioning/publishing entirely and always rebuild and upload the current stylesheet — the upload is idempotent, so this is always safe. ## Troubleshooting From 1aa11110811e8c7676615056b02646e0afd85ca8 Mon Sep 17 00:00:00 2001 From: Brian Herbert Date: Wed, 12 Aug 2026 13:20:12 -0500 Subject: [PATCH 4/7] fix: restrict Release workflow to main ref workflow_dispatch can be triggered from any ref containing the workflow file, which would let a branch build overwrite the production stylesheet. Guard the job to refs/heads/main so non-main dispatches are no-ops. Addresses Greptile security review feedback. Co-authored-by: Cursor --- .github/workflows/release.yml | 6 ++++++ PUBLISHING.md | 1 + 2 files changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 587488c1..aa3928a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,8 @@ on: # nothing newly published on the re-run). Instead, run this workflow manually # from main: the changesets step is skipped and the CDN upload always runs, # publishing the freshly built stylesheet. The upload is idempotent. + # Dispatches from any ref other than main are no-ops (job-level guard below), + # so branch CSS can never overwrite the production stylesheet. workflow_dispatch: concurrency: ${{ github.workflow }}-${{ github.ref }} @@ -22,6 +24,10 @@ permissions: jobs: release: name: Release + # Only ever run against main. Pushes are already filtered by the trigger; + # this guards workflow_dispatch, which GitHub allows from any ref that + # contains the workflow file. + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Checkout diff --git a/PUBLISHING.md b/PUBLISHING.md index 3aad9427..be61aa23 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -98,6 +98,7 @@ Repository Actions secrets: - On `main` pushes, the upload steps only run when `@youversion/platform-react-ui` is among the published packages. - npm publishing happens before the CDN upload; a failed upload does not affect the npm release. - **Do not retry by re-running the failed job**: on a re-run, changesets reports nothing newly published, so the CDN steps are skipped. Instead, after fixing the issue, trigger the **Release workflow manually** (Actions → Release → "Run workflow" on `main`). Manual runs skip versioning/publishing entirely and always rebuild and upload the current stylesheet — the upload is idempotent, so this is always safe. +- Manual dispatches from any ref other than `main` are no-ops (job-level guard), so branch CSS can never overwrite the production stylesheet. ## Troubleshooting From 6bb142ee3357a76e207c5c67c2ae5b4fd623a054 Mon Sep 17 00:00:00 2001 From: Brian Herbert Date: Wed, 12 Aug 2026 13:26:04 -0500 Subject: [PATCH 5/7] fix: build manual CDN re-uploads from the latest published UI tag A manual recovery run previously rebuilt current HEAD, which could contain merged-but-unreleased UI changes and push CSS ahead of the published npm package. Manual runs now check out the newest @youversion/platform-react-ui release tag before building, so the uploaded stylesheet always matches npm. Addresses Greptile review. Co-authored-by: Cursor --- .github/workflows/release.yml | 19 ++++++++++++++++++- PUBLISHING.md | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa3928a0..102a0d4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,8 @@ on: # already published to npm, a job re-run cannot retry it (changesets reports # nothing newly published on the re-run). Instead, run this workflow manually # from main: the changesets step is skipped and the CDN upload always runs, - # publishing the freshly built stylesheet. The upload is idempotent. + # built from the tag of the latest published UI release so the uploaded CSS + # matches what is on npm. The upload is idempotent. # Dispatches from any ref other than main are no-ops (job-level guard below), # so branch CSS can never overwrite the production stylesheet. workflow_dispatch: @@ -36,6 +37,22 @@ jobs: # This is required for changesets to access commit history fetch-depth: 0 + # On manual recovery runs, build the exact source of the latest published + # UI package instead of current HEAD: main may have advanced past the + # release commit, and the uploaded stylesheet must always match what is + # on npm. Changesets pushes a tag per published package version. + - name: Check out latest published UI release (manual runs) + if: github.event_name == 'workflow_dispatch' + run: | + git fetch --tags --quiet origin + tag=$(git tag --list '@youversion/platform-react-ui@*' --sort=-creatordate | head -n 1) + if [ -z "$tag" ]; then + echo "::error::No @youversion/platform-react-ui release tag found; cannot determine which stylesheet to publish." + exit 1 + fi + echo "Building stylesheet from $tag" + git checkout --detach "$tag" + - name: Setup pnpm uses: pnpm/action-setup@v4 diff --git a/PUBLISHING.md b/PUBLISHING.md index be61aa23..2beb719a 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -97,7 +97,7 @@ Repository Actions secrets: - On `main` pushes, the upload steps only run when `@youversion/platform-react-ui` is among the published packages. - npm publishing happens before the CDN upload; a failed upload does not affect the npm release. -- **Do not retry by re-running the failed job**: on a re-run, changesets reports nothing newly published, so the CDN steps are skipped. Instead, after fixing the issue, trigger the **Release workflow manually** (Actions → Release → "Run workflow" on `main`). Manual runs skip versioning/publishing entirely and always rebuild and upload the current stylesheet — the upload is idempotent, so this is always safe. +- **Do not retry by re-running the failed job**: on a re-run, changesets reports nothing newly published, so the CDN steps are skipped. Instead, after fixing the issue, trigger the **Release workflow manually** (Actions → Release → "Run workflow" on `main`). Manual runs skip versioning/publishing entirely; they check out the tag of the **latest published** `@youversion/platform-react-ui` release, rebuild it, and upload its stylesheet — so the CDN always matches what's on npm even if `main` has advanced past the release commit. The upload is idempotent, so this is always safe. - Manual dispatches from any ref other than `main` are no-ops (job-level guard), so branch CSS can never overwrite the production stylesheet. ## Troubleshooting From 7e399e1074c57a436feeb941f4ff90cab1774468 Mon Sep 17 00:00:00 2001 From: Brian Herbert Date: Wed, 12 Aug 2026 15:48:30 -0500 Subject: [PATCH 6/7] feat: gate CDN stylesheet upload on feature.platform.sdkCssCdn flag Per the Platform feature-flag conventions (transformers docs/feature_flags.md), the CDN upload now reads the feature.platform.sdkCssCdn flag from the prod Firebase Remote Config server template and skips the upload unless it is "true". The flag ships dark (prod: false) and is launched/killed from the Firebase console without a code change. Flag defined in transformers MR !599. Co-authored-by: Cursor --- .github/workflows/release.yml | 22 ++++++++++++++++++++++ PUBLISHING.md | 11 ++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 102a0d4e..45e7b68c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,6 +120,28 @@ jobs: - name: Publish UI stylesheet to CDN if: github.event_name == 'workflow_dispatch' || (steps.changesets.outputs.published == 'true' && contains(fromJSON(steps.changesets.outputs.publishedPackages).*.name, '@youversion/platform-react-ui')) run: | + # Feature-flag gate (feature.platform.sdkCssCdn, defined in the + # transformers flag manifest): read the prod Firebase Remote Config + # *server* template and only upload when the flag's value is "true". + # A missing parameter or "false" means CDN publishing is disabled + # (ship dark) — flip the value in the prod Firebase console to + # launch or kill without a code change. This simple check reads + # defaultValue only; conditional values / percentage rollouts are + # not evaluated. A template fetch failure fails this step (visibly) + # rather than guessing. + TOKEN="$(gcloud auth print-access-token)" + FLAG_VALUE="$(curl -sS --fail-with-body \ + -H "Authorization: Bearer $TOKEN" \ + "https://firebaseremoteconfig.googleapis.com/v1/projects/yvplatform-prod/namespaces/firebase-server/remoteConfig" \ + | jq -r '.parameters.feature_platform_sdkCssCdn.defaultValue.value // "false"')" + if [ "$FLAG_VALUE" != "true" ]; then + echo "::notice::CDN stylesheet publishing is disabled (feature.platform.sdkCssCdn=${FLAG_VALUE} in prod Remote Config); skipping upload." + echo "## CDN Stylesheet :art:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Skipped: \`feature.platform.sdkCssCdn\` is \`${FLAG_VALUE}\` in the prod Remote Config server template." >> $GITHUB_STEP_SUMMARY + exit 0 + fi + CSS_MAJOR_VERSION="$(tr -d '[:space:]' < packages/ui/CDN_CSS_MAJOR_VERSION)" test -s packages/ui/dist/tailwind.css gcloud storage cp packages/ui/dist/tailwind.css \ diff --git a/PUBLISHING.md b/PUBLISHING.md index 2beb719a..e3f9385a 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -76,13 +76,22 @@ The `` path segment is defined in **one place**: the `packages/ui/CDN_CSS - **Do not bump it for routine releases.** The file at `/platform//bible.css` is overwritten in place with each UI package release. - **Bump it only when the CSS changes in a breaking way** (selectors/variables/class names that existing consumers depend on are removed or behave differently). Bumping starts publishing to a new `/platform//bible.css` URL and leaves the old file untouched for existing consumers. +### Feature flag: `feature.platform.sdkCssCdn` + +The upload is gated by the `feature.platform.sdkCssCdn` feature flag, defined (like all YouVersion Platform flags) in the transformers repo's `src/flags.yaml` manifest and mirrored to Firebase Remote Config. The workflow reads the **prod server template** (project `yvplatform-prod`, namespace `firebase-server`, parameter key `feature_platform_sdkCssCdn` — Remote Config keys replace dots with underscores). + +- **Ship-dark default:** the flag is `false` in prod, so merging the workflow does not start publishing to the CDN by itself. +- **To launch (or kill) CDN publishing:** flip `feature_platform_sdkCssCdn` in the `yvplatform-prod` Firebase console's server template — no code change or deploy needed. A missing parameter evaluates as disabled. +- When the flag is off, the release still publishes to npm normally; the CDN step logs a notice and skips the upload. +- The check reads the parameter's default value only; conditional values / percentage rollouts are not evaluated. + ### How the upload authenticates The workflow uses Workload Identity Federation (OIDC) — no static GCP keys, matching how this repo publishes to npm via trusted publishing: 1. `google-github-actions/auth` exchanges the GitHub Actions OIDC token through the `github-actions-pool` Workload Identity Pool in the `yvplatform-prod` GCP project. 2. Only this repository (`youversion/platform-sdk-react`) may impersonate the dedicated service account `platform-sdk-cdn-publisher@yvplatform-prod.iam.gserviceaccount.com`. -3. That service account can only write objects under the `platform/` prefix of the `cdn-yv-platform-prod` origin bucket (IAM condition), nothing else. +3. That service account can only write objects under the `platform/` prefix of the `cdn-yv-platform-prod` origin bucket (IAM condition), plus read Firebase Remote Config (`roles/cloudconfig.viewer` on `yvplatform-prod`) for the feature-flag check — nothing else. Repository Actions secrets: From 168a60c54b013375d280efc188a54a6319549cbd Mon Sep 17 00:00:00 2001 From: Brian Herbert Date: Wed, 12 Aug 2026 15:54:56 -0500 Subject: [PATCH 7/7] fix: resolve manual recovery tag from the npm latest dist-tag Tag creation dates can lie (recreated tags, prereleases), so manual recovery runs now ask npm for the latest dist-tag version and check out the exact matching release tag, failing loudly if it does not exist. Addresses Greptile review feedback. Co-authored-by: Cursor --- .github/workflows/release.yml | 16 ++++++++++------ PUBLISHING.md | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 45e7b68c..66e05870 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,18 +40,22 @@ jobs: # On manual recovery runs, build the exact source of the latest published # UI package instead of current HEAD: main may have advanced past the # release commit, and the uploaded stylesheet must always match what is - # on npm. Changesets pushes a tag per published package version. + # on npm. The npm "latest" dist-tag is the source of truth for which + # version that is (git tag dates can lie: recreated tags or prereleases + # can carry the newest creation date); changesets pushes a matching + # git tag per published version. - name: Check out latest published UI release (manual runs) if: github.event_name == 'workflow_dispatch' run: | git fetch --tags --quiet origin - tag=$(git tag --list '@youversion/platform-react-ui@*' --sort=-creatordate | head -n 1) - if [ -z "$tag" ]; then - echo "::error::No @youversion/platform-react-ui release tag found; cannot determine which stylesheet to publish." + version="$(npm view @youversion/platform-react-ui version)" + tag="@youversion/platform-react-ui@${version}" + if ! git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then + echo "::error::npm latest is ${version} but tag ${tag} does not exist in this repository; cannot determine which stylesheet to publish." exit 1 fi - echo "Building stylesheet from $tag" - git checkout --detach "$tag" + echo "Building stylesheet from ${tag} (npm latest)" + git checkout --detach "refs/tags/${tag}" - name: Setup pnpm uses: pnpm/action-setup@v4 diff --git a/PUBLISHING.md b/PUBLISHING.md index e3f9385a..d90cdbdc 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -106,7 +106,7 @@ Repository Actions secrets: - On `main` pushes, the upload steps only run when `@youversion/platform-react-ui` is among the published packages. - npm publishing happens before the CDN upload; a failed upload does not affect the npm release. -- **Do not retry by re-running the failed job**: on a re-run, changesets reports nothing newly published, so the CDN steps are skipped. Instead, after fixing the issue, trigger the **Release workflow manually** (Actions → Release → "Run workflow" on `main`). Manual runs skip versioning/publishing entirely; they check out the tag of the **latest published** `@youversion/platform-react-ui` release, rebuild it, and upload its stylesheet — so the CDN always matches what's on npm even if `main` has advanced past the release commit. The upload is idempotent, so this is always safe. +- **Do not retry by re-running the failed job**: on a re-run, changesets reports nothing newly published, so the CDN steps are skipped. Instead, after fixing the issue, trigger the **Release workflow manually** (Actions → Release → "Run workflow" on `main`). Manual runs skip versioning/publishing entirely; they resolve the npm `latest` dist-tag of `@youversion/platform-react-ui`, check out the matching release tag, rebuild it, and upload its stylesheet — so the CDN always matches what's on npm even if `main` has advanced past the release commit. The upload is idempotent, so this is always safe. - Manual dispatches from any ref other than `main` are no-ops (job-level guard), so branch CSS can never overwrite the production stylesheet. ## Troubleshooting