Skip to content

Commit 1c6a674

Browse files
committed
script
1 parent 6b8e9f4 commit 1c6a674

7 files changed

Lines changed: 91 additions & 23 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
runs-on: ubuntu-latest
4545
strategy:
4646
matrix:
47-
react-version: ["^19.0.0", "^19.1.0", "^19.2.0", "latest"]
47+
react-version: ["~19.0.0", "~19.1.0", "~19.2.0", "latest"]
4848
steps:
4949
- uses: actions/checkout@v5
5050

.github/workflows/nightly.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
runs-on: ubuntu-latest
2727
strategy:
2828
matrix:
29-
react-version: ["^19.0.0", "^19.1.0", "^19.2.0", "latest"]
29+
react-version: ["~19.0.0", "~19.1.0", "~19.2.0", "latest"]
3030
steps:
3131
- uses: actions/checkout@v5
3232

docs/agents/commands.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Use Bun to run repository scripts.
88
- `bun run test` runs the Jest test suite.
99
- `bun run test:ci` runs tests with coverage.
1010
- `bun run lint` runs ESLint.
11-
- `bun run prettier` checks formatting.
12-
- `bun run prettier:fix` writes formatting changes.
11+
- `bun run format` checks formatting.
12+
- `bun run format:fix` writes formatting changes.
1313
- `bun run validate` runs typecheck, test, lint, and formatting checks.
1414
- `bun run validate:fix` runs formatting and lint fixes, then typecheck and tests.

eslint.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ import simpleImportSort from "eslint-plugin-simple-import-sort";
55
export default tseslint.config(
66
eslint.configs.recommended,
77
...tseslint.configs.strictTypeChecked,
8+
{
9+
files: ["**/*.{js,mjs,cjs}"],
10+
...tseslint.configs.disableTypeChecked,
11+
languageOptions: {
12+
globals: {
13+
console: "readonly",
14+
fetch: "readonly",
15+
process: "readonly",
16+
},
17+
},
18+
},
819
{
920
files: ["src/**/*.{ts,tsx}"],
1021
languageOptions: {

package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@
4343
"lint": "eslint .",
4444
"typecheck": "tsc --noEmit",
4545
"test:watch": "jest --watch",
46-
"validate": "bun run typecheck && bun run test && bun run lint && bun run prettier",
47-
"validate:fix": "bun run prettier:fix && bun run lint --fix && bun run typecheck && bun run test -u",
48-
"prettier": "prettier --check .",
49-
"prettier:fix": "prettier --write .",
46+
"validate": "bun run typecheck && bun run test && bun run lint && bun run format",
47+
"validate:fix": "bun run format:fix && bun run lint --fix && bun run typecheck && bun run test -u",
48+
"format": "prettier --check .",
49+
"format:fix": "prettier --write .",
5050
"release": "release-it"
5151
},
5252
"dependencies": {
@@ -56,6 +56,9 @@
5656
"peerDependencies": {
5757
"react": "^19.0.0"
5858
},
59+
"testRenderer": {
60+
"supportedReactRange": ">=19.0.0 <19.3.0"
61+
},
5962
"devDependencies": {
6063
"@eslint/js": "^9.21.0",
6164
"@jest/globals": "^30.2.0",

scripts/check-react-release-guard.mjs

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import packageJson from "../package.json" with { type: "json" };
22

3+
function getSupportedReactRange() {
4+
const supportedReactRange = packageJson.testRenderer?.supportedReactRange;
5+
6+
if (typeof supportedReactRange !== "string" || supportedReactRange.trim() === "") {
7+
throw new Error("package.json must define testRenderer.supportedReactRange");
8+
}
9+
10+
return supportedReactRange;
11+
}
12+
313
function parseSemver(version) {
414
const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version);
515

@@ -71,13 +81,11 @@ function parseExclusiveUpperBounds(range) {
7181
return upperBounds;
7282
}
7383

74-
function getReactSupportWindow(range) {
84+
function getSupportedReactCeiling(range) {
7585
const upperBounds = parseExclusiveUpperBounds(range);
7686

7787
if (upperBounds.length === 0) {
78-
throw new Error(
79-
`Could not derive a supported React ceiling from peerDependencies.react: ${range}`,
80-
);
88+
throw new Error(`Could not derive a supported React ceiling from range: ${range}`);
8189
}
8290

8391
return upperBounds.reduce((lowest, candidate) =>
@@ -89,16 +97,16 @@ function formatSemver(version) {
8997
return `${version.major}.${version.minor}.${version.patch}`;
9098
}
9199

92-
function getSupportedLineLabel(upperBound) {
100+
function getSupportedLinesLabel(upperBound) {
93101
if (upperBound.patch === 0 && upperBound.minor > 0) {
94-
return `${upperBound.major}.${upperBound.minor - 1}.x`;
102+
return `React ${upperBound.major}.0.x through ${upperBound.major}.${upperBound.minor - 1}.x`;
95103
}
96104

97105
if (upperBound.minor === 0 && upperBound.patch === 0) {
98-
return `${upperBound.major - 1}.x`;
106+
return `React ${upperBound.major - 1}.x`;
99107
}
100108

101-
return `<${formatSemver(upperBound)}`;
109+
return `React versions <${formatSemver(upperBound)}`;
102110
}
103111

104112
async function getLatestReactVersion() {
@@ -130,20 +138,24 @@ async function main() {
130138
const latestVersion = await getLatestReactVersion();
131139
const parsedLatest = parseSemver(latestVersion);
132140
const peerRange = packageJson.peerDependencies?.react ?? "<missing>";
133-
const upperBound = getReactSupportWindow(peerRange);
134-
const supportedLineLabel = getSupportedLineLabel(upperBound);
141+
const supportedReactRange = getSupportedReactRange();
142+
const supportedReactCeiling = getSupportedReactCeiling(supportedReactRange);
143+
const supportedLinesLabel = getSupportedLinesLabel(supportedReactCeiling);
135144

136-
if (compareSemver(parsedLatest, upperBound) < 0) {
137-
console.log(`React ${latestVersion} is still within the supported line (${supportedLineLabel}).`);
145+
if (compareSemver(parsedLatest, supportedReactCeiling) < 0) {
146+
console.log(
147+
`React ${latestVersion} is still within the supported window (${supportedLinesLabel}).`,
148+
);
138149
return;
139150
}
140151

141152
const failureLines = [
142-
`React ${latestVersion} has been released on npm, but this repository only supports up to React ${supportedLineLabel}.`,
153+
`React ${latestVersion} has been released on npm, but this repository currently supports only ${supportedLinesLabel}.`,
143154
`A plain npm install of test-renderer is no longer safely constrained for the newest React release.`,
144155
`Current peerDependencies.react: ${peerRange}`,
145-
`Current derived React ceiling: <${formatSemver(upperBound)}`,
146-
`Update the compatibility policy, add support for the new React line, and tighten the published peer dependency range before relying on latest again.`,
156+
`Configured supportedReactRange: ${supportedReactRange}`,
157+
`Current derived React ceiling: <${formatSemver(supportedReactCeiling)}`,
158+
`Update testRenderer.supportedReactRange or tighten peerDependencies.react before relying on latest again.`,
147159
];
148160

149161
throw new Error(failureLines.join("\n"));
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { spawnSync } from "node:child_process";
2+
import { resolve } from "node:path";
3+
import { describe, expect, test } from "@jest/globals";
4+
5+
const scriptPath = resolve(process.cwd(), "scripts/check-react-release-guard.mjs");
6+
7+
describe("react release guard", () => {
8+
test("accepts the latest release within the supported React line", () => {
9+
const result = spawnSync(process.execPath, [scriptPath], {
10+
encoding: "utf8",
11+
env: {
12+
...process.env,
13+
REACT_RELEASE_GUARD_LATEST: "19.2.5",
14+
},
15+
});
16+
17+
expect(result.status).toBe(0);
18+
expect(result.stdout).toContain(
19+
"React 19.2.5 is still within the supported window (React 19.0.x through 19.2.x).",
20+
);
21+
expect(result.stderr).toBe("");
22+
});
23+
24+
test("fails when the latest release moves past the supported React line", () => {
25+
const result = spawnSync(process.execPath, [scriptPath], {
26+
encoding: "utf8",
27+
env: {
28+
...process.env,
29+
REACT_RELEASE_GUARD_LATEST: "19.3.0",
30+
},
31+
});
32+
33+
expect(result.status).toBe(1);
34+
expect(result.stdout).toBe("");
35+
expect(result.stderr).toContain(
36+
"React 19.3.0 has been released on npm, but this repository currently supports only React 19.0.x through 19.2.x.",
37+
);
38+
expect(result.stderr).toContain("Current peerDependencies.react: ^19.0.0");
39+
expect(result.stderr).toContain("Configured supportedReactRange: >=19.0.0 <19.3.0");
40+
expect(result.stderr).toContain("Current derived React ceiling: <19.3.0");
41+
});
42+
});

0 commit comments

Comments
 (0)