From 6fa20caf3d0aa04e002706aa55258921d696a059 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 18:55:45 +0000 Subject: [PATCH 1/5] ci: Upload coverage with Codecov's verified orb and enforce coverage floors Replace the unverified `latest` download of the deprecated Codecov uploader with codecov/codecov@6.1.0 pinned to CLI v11.3.1, which GPG-verifies the binary before running it with CODECOV_TOKEN. test:coverage now fails below 98% statements/lines, 96% branches, 90% functions. Also fix broken badge links in the normalizr README. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_018P4fjuXtiX2DKsW3iJtQH9 --- .circleci/config.yml | 24 ++++++++++++++---------- package.json | 2 +- packages/normalizr/README.md | 6 +++--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 58465fd2d66d..c97b1834f7a1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,8 @@ # Check https://circleci.com/docs/2.0/language-javascript/ for more details # version: 2.1 +orbs: + codecov: codecov/codecov@6.1.0 executors: node: docker: &docker @@ -252,17 +254,19 @@ jobs: yarn test:ci --selectProjects ReactNative elif [ "<< parameters.react-version >>" == "19.3" ]; then yarn test:ci --maxWorkers=4 --selectProjects ReactDOM --testPathPatterns 'packages/react/src/__tests__/concurrent-.*\.web\.tsx' 'packages/react/src/hooks/__tests__/useCacheState.web.tsx' - else - curl -fsSO --connect-timeout 10 --max-time 60 https://uploader.codecov.io/latest/linux/codecov && chmod +x codecov || echo "Codecov uploader download failed; skipping coverage upload"; - yarn run test:coverage --ci --maxWorkers=4 --selectProjects ReactDOM Node --coverageReporters=text-lcov > ./lcov.info; - if [ -x ./codecov ]; then - if [ "$CODECOV_TOKEN" != "" ]; then - ./codecov -t ${CODECOV_TOKEN} < ./lcov.info || true; - else - ./codecov < ./lcov.info || true; - fi - fi + elif [ "<< parameters.react-version >>" == "latest" ]; then + # Coverage floors live in the test:coverage script + yarn run test:coverage --ci --maxWorkers=4 --selectProjects ReactDOM Node --coverageReporters=lcovonly --coverageReporters=text-summary fi + - when: + condition: + equal: [latest, << parameters.react-version >>] + steps: + # The orb GPG-verifies the pinned CLI before running it with CODECOV_TOKEN + - codecov/upload: + version: v11.3.1 + files: ./coverage/lcov.info + disable_search: true node_matrix: parameters: diff --git a/package.json b/package.json index 1eeff5aa957e..8de2033ccc24 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "build:skills": "yarn workspace rdc-website build:skills", "test": "NODE_ENV=test run jest", "test:ci": "ANANSI_JEST_TYPECHECK=false yarn test --ci", - "test:coverage": "ANANSI_JEST_TYPECHECK=false yarn test --coverage", + "test:coverage": "ANANSI_JEST_TYPECHECK=false yarn test --coverage --coverageThreshold='{\"global\":{\"statements\":98,\"branches\":96,\"functions\":90,\"lines\":98}}'", "prepare": "yarn build:copy:ambient && tsc --build --builders 1", "prepack": "yarn prepare", "prepublishOnly": "yarn workspaces foreach -Wpti --no-private run build:legacy-types", diff --git a/packages/normalizr/README.md b/packages/normalizr/README.md index 968464c1238d..54f3f9f9b787 100644 --- a/packages/normalizr/README.md +++ b/packages/normalizr/README.md @@ -1,10 +1,10 @@ # Normalizr Client -[![CircleCI](https://circleci.com/gh/reactive/data-client/tree/master.svg?style=shield)](https://circleci.com/gh/data-clientdata-clients) -[![Coverage Status](https://img.shields.io/codecov/c/gh/reactive/data-client/master.svg?style=flat-square)](https://app.codecov.io/gh/data-clientdata-clients?branch=master) +[![CircleCI](https://circleci.com/gh/reactive/data-client/tree/master.svg?style=shield)](https://circleci.com/gh/reactive/data-client) +[![Coverage Status](https://img.shields.io/codecov/c/gh/reactive/data-client/master.svg?style=flat-square)](https://app.codecov.io/gh/reactive/data-client?branch=master) [![npm downloads](https://img.shields.io/npm/dt/@data-client/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/@data-client/normalizr) [![bundle size](https://img.shields.io/bundlephobia/minzip/@data-client/normalizr?style=flat-square)](https://bundlephobia.com/result?p=@data-client/normalizr) -[![npm version](https://img.shields.io/npm/v/@data-client/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/data-clients/normalizr) [![npm downloads](https://img.shields.io/npm/dm/@data-client/normalizr.svg?style=flat-square)](https://www.npmjs.com/packagdata-clientoks/normalizr) +[![npm version](https://img.shields.io/npm/v/@data-client/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/@data-client/normalizr) [![npm downloads](https://img.shields.io/npm/dm/@data-client/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/@data-client/normalizr) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) ## Install From ee2cff5f69d233f68450c2f4200f56b1476ec73f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 18:58:14 +0000 Subject: [PATCH 2/5] ci: Verify the Codecov CLI inline instead of via the orb The org doesn't allow uncertified public orbs, so the pipeline failed to compile. Download the pinned CLI and check its signed SHA256SUM against Codecov's key fingerprint directly, and upload even when coverage floors fail. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_018P4fjuXtiX2DKsW3iJtQH9 --- .circleci/config.yml | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c97b1834f7a1..7e6f9fc71259 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,8 +3,6 @@ # Check https://circleci.com/docs/2.0/language-javascript/ for more details # version: 2.1 -orbs: - codecov: codecov/codecov@6.1.0 executors: node: docker: &docker @@ -262,11 +260,34 @@ jobs: condition: equal: [latest, << parameters.react-version >>] steps: - # The orb GPG-verifies the pinned CLI before running it with CODECOV_TOKEN - - codecov/upload: - version: v11.3.1 - files: ./coverage/lcov.info - disable_search: true + - run: + name: Upload coverage to Codecov + # Upload even when the coverage floors fail, so Codecov shows what dropped + when: always + command: | + # Pinned CLI, checked against Codecov's signing key fingerprint + # before it ever sees CODECOV_TOKEN. + # https://docs.codecov.com/docs/codecov-uploader#integrity-checking-the-uploader + CLI=https://cli.codecov.io/v11.3.1/linux/codecov + KEY_FINGERPRINT=27034E7FDB850E0BBC2C62FF806BB28AED779869 + DIR="$(mktemp -d)" + export GNUPGHOME="$DIR/gnupg" + mkdir -m 700 "$GNUPGHOME" + # gpg status goes to a file, not a pipe: CircleCI's pipefail would + # flip the check if an early-exiting grep SIGPIPEd gpg. + if (cd "$DIR" && curl -fsS --retry 3 -O "$CLI" -O "$CLI.SHA256SUM" -O "$CLI.SHA256SUM.sig") \ + && curl -fsS --retry 3 -o "$DIR/key.asc" https://keybase.io/codecovsecops/pgp_keys.asc \ + && gpg --batch --import "$DIR/key.asc" \ + && gpg --batch --status-file "$DIR/status" --verify "$DIR/codecov.SHA256SUM.sig" "$DIR/codecov.SHA256SUM" \ + && grep -q "VALIDSIG .*$KEY_FINGERPRINT" "$DIR/status" \ + && (cd "$DIR" && sha256sum -c codecov.SHA256SUM); then + chmod +x "$DIR/codecov" + # Fork PRs have no token; Codecov accepts tokenless uploads from them + "$DIR/codecov" upload-coverage ${CODECOV_TOKEN:+-t "$CODECOV_TOKEN"} --file coverage/lcov.info --disable-search \ + || echo "Codecov upload failed" + else + echo "Codecov CLI download or signature check failed; skipping upload" + fi node_matrix: parameters: From d3d3e2b1d34a321562ce9445b373999b4a00dcc4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 19:00:24 +0000 Subject: [PATCH 3/5] ci: Let Renovate bump the pinned Codecov CLI Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_018P4fjuXtiX2DKsW3iJtQH9 --- .circleci/config.yml | 1 + .github/renovate.json | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7e6f9fc71259..d2db30c32248 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -268,6 +268,7 @@ jobs: # Pinned CLI, checked against Codecov's signing key fingerprint # before it ever sees CODECOV_TOKEN. # https://docs.codecov.com/docs/codecov-uploader#integrity-checking-the-uploader + # renovate: datasource=github-releases depName=codecov/codecov-cli CLI=https://cli.codecov.io/v11.3.1/linux/codecov KEY_FINGERPRINT=27034E7FDB850E0BBC2C62FF806BB28AED779869 DIR="$(mktemp -d)" diff --git a/.github/renovate.json b/.github/renovate.json index 83ab66d08a91..3d8bfb3530f9 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -177,5 +177,15 @@ "groupName": "node", "groupSlug": "node" } + ], + "customManagers": [ + { + "description": "Codecov CLI pinned in CircleCI's coverage upload step", + "customType": "regex", + "managerFilePatterns": ["/^\\.circleci/config\\.yml$/"], + "matchStrings": [ + "# renovate: datasource=(?\\S+) depName=(?\\S+)\\s+CLI=https://cli\\.codecov\\.io/(?v[^/]+)/" + ] + } ] } From 188d35e7dc65db7b7ed4dfdbda162227b883a070 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 19:02:13 +0000 Subject: [PATCH 4/5] ci: Tell the Codecov CLI the git service and skip its unused plugins Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_018P4fjuXtiX2DKsW3iJtQH9 --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d2db30c32248..8e4a7764c2c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -284,7 +284,8 @@ jobs: && (cd "$DIR" && sha256sum -c codecov.SHA256SUM); then chmod +x "$DIR/codecov" # Fork PRs have no token; Codecov accepts tokenless uploads from them - "$DIR/codecov" upload-coverage ${CODECOV_TOKEN:+-t "$CODECOV_TOKEN"} --file coverage/lcov.info --disable-search \ + "$DIR/codecov" upload-coverage ${CODECOV_TOKEN:+-t "$CODECOV_TOKEN"} --git-service github \ + --file coverage/lcov.info --disable-search --plugin noop \ || echo "Codecov upload failed" else echo "Codecov CLI download or signature check failed; skipping upload" From a27a8d01c71ddaaffa7c8637b6821c780b815972 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 19:21:24 +0000 Subject: [PATCH 5/5] ci: Simplify the Codecov upload step and align test:coverage with CI Fetch all four files in one curl, reuse the mktemp dir as GNUPGHOME, skip the upload when no report exists, fail loudly on an unknown react-version, and move --selectProjects into test:coverage so local runs measure the same projects as CI. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_018P4fjuXtiX2DKsW3iJtQH9 --- .circleci/config.yml | 18 ++++++++++++------ package.json | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e4a7764c2c0..4508ed2b7593 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -253,8 +253,11 @@ jobs: elif [ "<< parameters.react-version >>" == "19.3" ]; then yarn test:ci --maxWorkers=4 --selectProjects ReactDOM --testPathPatterns 'packages/react/src/__tests__/concurrent-.*\.web\.tsx' 'packages/react/src/hooks/__tests__/useCacheState.web.tsx' elif [ "<< parameters.react-version >>" == "latest" ]; then - # Coverage floors live in the test:coverage script - yarn run test:coverage --ci --maxWorkers=4 --selectProjects ReactDOM Node --coverageReporters=lcovonly --coverageReporters=text-summary + # Coverage floors and projects live in the test:coverage script + yarn run test:coverage --ci --maxWorkers=4 --coverageReporters=lcovonly --coverageReporters=text-summary + else + echo "Unknown react-version << parameters.react-version >>" + exit 1 fi - when: condition: @@ -271,13 +274,16 @@ jobs: # renovate: datasource=github-releases depName=codecov/codecov-cli CLI=https://cli.codecov.io/v11.3.1/linux/codecov KEY_FINGERPRINT=27034E7FDB850E0BBC2C62FF806BB28AED779869 + if [ ! -f coverage/lcov.info ]; then + echo "No coverage report; skipping upload" + exit 0 + fi DIR="$(mktemp -d)" - export GNUPGHOME="$DIR/gnupg" - mkdir -m 700 "$GNUPGHOME" + export GNUPGHOME="$DIR" # gpg status goes to a file, not a pipe: CircleCI's pipefail would # flip the check if an early-exiting grep SIGPIPEd gpg. - if (cd "$DIR" && curl -fsS --retry 3 -O "$CLI" -O "$CLI.SHA256SUM" -O "$CLI.SHA256SUM.sig") \ - && curl -fsS --retry 3 -o "$DIR/key.asc" https://keybase.io/codecovsecops/pgp_keys.asc \ + if curl -fsS --retry 3 --output-dir "$DIR" -O "$CLI" -O "$CLI.SHA256SUM" -O "$CLI.SHA256SUM.sig" \ + -o key.asc https://keybase.io/codecovsecops/pgp_keys.asc \ && gpg --batch --import "$DIR/key.asc" \ && gpg --batch --status-file "$DIR/status" --verify "$DIR/codecov.SHA256SUM.sig" "$DIR/codecov.SHA256SUM" \ && grep -q "VALIDSIG .*$KEY_FINGERPRINT" "$DIR/status" \ diff --git a/package.json b/package.json index 8de2033ccc24..24d9c25df12a 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "build:skills": "yarn workspace rdc-website build:skills", "test": "NODE_ENV=test run jest", "test:ci": "ANANSI_JEST_TYPECHECK=false yarn test --ci", - "test:coverage": "ANANSI_JEST_TYPECHECK=false yarn test --coverage --coverageThreshold='{\"global\":{\"statements\":98,\"branches\":96,\"functions\":90,\"lines\":98}}'", + "test:coverage": "ANANSI_JEST_TYPECHECK=false yarn test --coverage --selectProjects ReactDOM Node --coverageThreshold='{\"global\":{\"statements\":98,\"branches\":96,\"functions\":90,\"lines\":98}}'", "prepare": "yarn build:copy:ambient && tsc --build --builders 1", "prepack": "yarn prepare", "prepublishOnly": "yarn workspaces foreach -Wpti --no-private run build:legacy-types",