From 02510785dae180d4392c797690f5b6628ab75a53 Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Tue, 5 May 2026 15:42:42 +0100 Subject: [PATCH 01/11] Codemod installed and migration doc --- REACT_19_MIGRATION.md | 49 +++ package-lock.json | 898 ++++++++++++++++++++++++++++++++++++++++-- package.json | 2 + 3 files changed, 915 insertions(+), 34 deletions(-) create mode 100644 REACT_19_MIGRATION.md diff --git a/REACT_19_MIGRATION.md b/REACT_19_MIGRATION.md new file mode 100644 index 0000000000..352f7d1855 --- /dev/null +++ b/REACT_19_MIGRATION.md @@ -0,0 +1,49 @@ +# React 19 Migration — `@skyscanner/backpack-web` + +This repo is **Repo 1 of 5** in the Skyscanner shared-library React 19 pre-release codemod pass. + +**Master plan**: [`/Users/SimonWard/Documents/Code/legal-pages/REACT_19_SHARED_LIBRARY_MIGRATION.md`](../legal-pages/REACT_19_SHARED_LIBRARY_MIGRATION.md) — see the **`Repo 1 — @skyscanner/backpack-web`** section. + +## What this thread should do + +1. Read the master plan's "Repo 1" section. It contains the full audit baseline, recipe checklist, and empty result fields to fill in. +2. Execute the recipe inside this directory. +3. As you progress, update the master plan's Repo 1 section with results (codemod diff summary, test outcomes, surprises). +4. When done, append a one-paragraph "Surprises / runbook insights" entry, and open follow-up PRs against `web-documentation` and `web-migration-scripts` if any reusable insights came out. + +## Audit baseline (2026-05-05) + +This is the **largest surface** of the 5 libraries. Approach incrementally. + +- Lerna-style monorepo, published from `packages/package.json` +- React peerDep: `17.0.2 - 18.3.1` (must widen to `^17.0.2 || ^18.3.1 || ^19.2.5`) +- `static defaultProps` / `defaultProps =`: **84 files** (manual sweep, no codemod) +- `forwardRef`: 14 files (none combined with `defaultProps`) +- `act` from `react-dom/test-utils`: 7 test files +- `prop-types` imports: **67 files** (codemod converts to TS types) +- `ReactDOM.render` / `hydrate`: 1 file +- String refs / `findDOMNode` / legacy context: 1 file (manual review) +- Zero-arg `useRef()`: 0 +- TS 5.9.2; `@types/react` 18.3.1 +- Tests: Jest + `@testing-library/react` 16.3.0 +- CI: `.github/workflows/{release,pr,main}.yml` — `npm publish` after transpile + +## Recipe summary (full detail in master plan) + +1. Pin `codemod` + `types-react-codemod` as devDeps with `--save-exact` (no `npx`/`pnpm dlx` — Skyscanner Security stance). +2. `./node_modules/.bin/codemod react/19/migration-recipe` +3. `./node_modules/.bin/types-react-codemod preset-19 .` +4. `./node_modules/.bin/types-react-codemod react-element-default-any-props .` +5. Manual `defaultProps` sweep (84 files — track per-file in PR description). +6. Manual review of the 1 string-ref/findDOMNode/legacy-context site and the 1 `ReactDOM.render` site. +7. `forwardRef` ref-callback implicit-return scan. +8. Widen `peerDependencies` in `packages/package.json` to `^17.0.2 || ^18.3.1 || ^19.2.5`. +9. Add CI matrix entry running tests against React 19.2.5. +10. Uninstall codemod packages. +11. Coordinate version bump with the Backpack team. + +## Reference docs + +- [`/Users/SimonWard/Documents/Code/legal-pages/REACT_19_UPGRADE_GUIDE.md`](../legal-pages/REACT_19_UPGRADE_GUIDE.md) — codemod commands, manual migration patterns, troubleshooting +- [`/Users/SimonWard/Documents/Code/legal-pages/REACT_19_MIGRATION_REQUIREMENTS.md`](../legal-pages/REACT_19_MIGRATION_REQUIREMENTS.md) — peer-dep audit and breaking-changes inventory +- `/Users/SimonWard/Documents/Code/web-migration-scripts/migrations/2024-04-react-18_3_1/` — structural template for the migration directory we'll build at `2026-05-react-19/` diff --git a/package-lock.json b/package-lock.json index b2858ad3f4..4e767ebafb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "babel-plugin-module-resolver": "^5.0.0", "babel-plugin-react-docgen": "^4.2.1", "babel-plugin-require-context-hook": "^1.0.0", + "codemod": "1.9.1", "core-js": "^3.47.0", "css-loader": "^7.1.2", "d3-scale": "^4.0.2", @@ -82,6 +83,7 @@ "storybook": "^10.3.3", "style-loader": "^4.0.0", "ts-migrate": "^0.1.35", + "types-react-codemod": "3.5.3", "typescript": "^5.9.2", "webpack": "^5.103.0", "wrapper-webpack-plugin": "^2.2.2" @@ -498,9 +500,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -877,11 +879,13 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.22.5", + "version": "7.28.6", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", + "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1388,12 +1392,14 @@ } }, "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.22.5", + "version": "7.27.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-flow": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2166,13 +2172,15 @@ } }, "node_modules/@babel/preset-flow": { - "version": "7.22.15", + "version": "7.27.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@babel/preset-flow/-/preset-flow-7.27.1.tgz", + "integrity": "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", - "@babel/plugin-transform-flow-strip-types": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-flow-strip-types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2386,6 +2394,91 @@ "@keyv/serialize": "^1.1.1" } }, + "node_modules/@codemod.com/cli-darwin-arm64": { + "version": "1.9.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-darwin-arm64/-/cli-darwin-arm64-1.9.1.tgz", + "integrity": "sha512-WnSVAV9WC5sVUjc24a3FhaLXkdF78D0gofuKKhRpdcc7hloH6YH6lticiv0O4xE3ba4+URLiGjUUT55hn9cWAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@codemod.com/cli-darwin-x64": { + "version": "1.9.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-darwin-x64/-/cli-darwin-x64-1.9.1.tgz", + "integrity": "sha512-t8pWmKJgSbwwXR3D7Q36vemYkvI44lekl1hdr/Gw5ysKKi0BBg8VTotjopZmwn8AJ1uMqVG4YjMzzxrM8QdmyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@codemod.com/cli-linux-arm64-gnu": { + "version": "1.9.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.9.1.tgz", + "integrity": "sha512-mpofqJUJS6QKgav+XctJvBJvgOHZndFcwzzG8NK176scEKPU/WaY20nc410RG06S1QPK1biH8UkPFn6cp98oeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@codemod.com/cli-linux-x64-gnu": { + "version": "1.9.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.9.1.tgz", + "integrity": "sha512-xnV+PmkTMSFVGuxmQB7/BzJJVrMdTfgt75DVeXjAhrVzhvbNo6dlDdz6orRslPnA5t2C46NJqSQMjrZCzzUCBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@codemod.com/cli-win32-x64-msvc": { + "version": "1.9.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.9.1.tgz", + "integrity": "sha512-VVsezk2HHLLYblPRnhu2p22WXDDg48lwIWItGNQ2sb2HJQTEaUbVv9f9ilRx9Rl0x0Q/70mQsT44n9iQ8f8XFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", @@ -3766,35 +3859,478 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@inquirer/core/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/core/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, "engines": { - "node": ">=12.22" + "node": ">=18" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", "dev": true, - "license": "BSD-3-Clause" + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=18.18" + "node": ">=18" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@isaacs/cliui": { @@ -9681,6 +10217,13 @@ "node": ">=10" } }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -9905,6 +10448,16 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -10063,6 +10616,39 @@ "dev": true, "license": "MIT" }, + "node_modules/codemod": { + "version": "1.9.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/codemod/-/codemod-1.9.1.tgz", + "integrity": "sha512-Uff4wQGl9YgbBy5P3V65WqkkvgtJfXr5uFvMztDjyHdX66yiGZETukj7oHG1dlQV4sDEdP+jIThKyWSMz+1NOg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "bin": { + "codemod": "codemod" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "@codemod.com/cli-darwin-arm64": "1.9.1", + "@codemod.com/cli-darwin-x64": "1.9.1", + "@codemod.com/cli-linux-arm64-gnu": "1.9.1", + "@codemod.com/cli-linux-x64-gnu": "1.9.1", + "@codemod.com/cli-win32-x64-msvc": "1.9.1" + } + }, + "node_modules/codemod/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -15566,6 +16152,33 @@ "dev": true, "license": "ISC" }, + "node_modules/inquirer": { + "version": "12.11.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/inquirer/-/inquirer-12.11.1.tgz", + "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/prompts": "^7.10.1", + "@inquirer/type": "^3.0.10", + "mute-stream": "^2.0.0", + "run-async": "^4.0.6", + "rxjs": "^7.8.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -20626,6 +21239,16 @@ "node": ">= 10.13.0" } }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/nano-spawn": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", @@ -23866,6 +24489,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/run-async": { + "version": "4.0.6", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/run-async/-/run-async-4.0.6.tgz", + "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -23889,10 +24522,11 @@ } }, "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "version": "7.8.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -26989,6 +27623,16 @@ "dev": true, "license": "MIT" }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -27921,6 +28565,179 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/types-react-codemod": { + "version": "3.5.3", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/types-react-codemod/-/types-react-codemod-3.5.3.tgz", + "integrity": "sha512-3J4TMIicQL2G0UrQ0OF5ZmkWK5IVNUPbZ7GSzuKWMxB0SkGEJ+0XJnKnVR75l2g0kUxFugakh22/ptnbukPQuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.17.8", + "@babel/parser": "^7.17.8", + "@babel/preset-env": "^7.16.11", + "@babel/types": "^7.17.8", + "inquirer": "^12.0.0", + "jscodeshift": "^17.0.0", + "yargs": "^17.4.0" + }, + "bin": { + "types-react-codemod": "bin/types-react-codemod.cjs" + }, + "engines": { + "node": "18.x || 20.x || 22.x" + } + }, + "node_modules/types-react-codemod/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/types-react-codemod/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/types-react-codemod/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/types-react-codemod/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/types-react-codemod/node_modules/jscodeshift": { + "version": "17.3.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/jscodeshift/-/jscodeshift-17.3.0.tgz", + "integrity": "sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/preset-flow": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@babel/register": "^7.24.6", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.7", + "neo-async": "^2.5.0", + "picocolors": "^1.0.1", + "recast": "^0.23.11", + "tmp": "^0.2.3", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + }, + "peerDependenciesMeta": { + "@babel/preset-env": { + "optional": true + } + } + }, + "node_modules/types-react-codemod/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/types-react-codemod/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/types-react-codemod/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/types-react-codemod/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -29647,6 +30464,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.25.58", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.58.tgz", diff --git a/package.json b/package.json index 38e96da961..925bead505 100644 --- a/package.json +++ b/package.json @@ -149,6 +149,7 @@ "babel-plugin-module-resolver": "^5.0.0", "babel-plugin-react-docgen": "^4.2.1", "babel-plugin-require-context-hook": "^1.0.0", + "codemod": "1.9.1", "core-js": "^3.47.0", "css-loader": "^7.1.2", "d3-scale": "^4.0.2", @@ -182,6 +183,7 @@ "storybook": "^10.3.3", "style-loader": "^4.0.0", "ts-migrate": "^0.1.35", + "types-react-codemod": "3.5.3", "typescript": "^5.9.2", "webpack": "^5.103.0", "wrapper-webpack-plugin": "^2.2.2" From de37bdfd71d022aad120695ebd8ae667b25b13be Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Tue, 5 May 2026 16:14:55 +0100 Subject: [PATCH 02/11] Changes to plan after re-audit --- REACT_19_MIGRATION.md | 50 ++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/REACT_19_MIGRATION.md b/REACT_19_MIGRATION.md index 352f7d1855..4a5dfd197e 100644 --- a/REACT_19_MIGRATION.md +++ b/REACT_19_MIGRATION.md @@ -11,36 +11,52 @@ This repo is **Repo 1 of 5** in the Skyscanner shared-library React 19 pre-relea 3. As you progress, update the master plan's Repo 1 section with results (codemod diff summary, test outcomes, surprises). 4. When done, append a one-paragraph "Surprises / runbook insights" entry, and open follow-up PRs against `web-documentation` and `web-migration-scripts` if any reusable insights came out. -## Audit baseline (2026-05-05) +## Audit baseline (2026-05-05) — corrected after re-audit on 2026-05-05 -This is the **largest surface** of the 5 libraries. Approach incrementally. +The original baseline overstated several categories. Re-audit confirmed: - Lerna-style monorepo, published from `packages/package.json` - React peerDep: `17.0.2 - 18.3.1` (must widen to `^17.0.2 || ^18.3.1 || ^19.2.5`) - `static defaultProps` / `defaultProps =`: **84 files** (manual sweep, no codemod) - `forwardRef`: 14 files (none combined with `defaultProps`) -- `act` from `react-dom/test-utils`: 7 test files -- `prop-types` imports: **67 files** (codemod converts to TS types) -- `ReactDOM.render` / `hydrate`: 1 file -- String refs / `findDOMNode` / legacy context: 1 file (manual review) +- `prop-types` imports: **67 files** (mostly `.js`; manual conversion — see runbook insight below) +- `act` from `react-dom/test-utils`: **0** (audit said 7; re-grep finds none) +- `ReactDOM.render` / `hydrate`: **0** (audit said 1; the only hit is example text inside a `` JSX literal in `BpkCode.stories.tsx`, not a real call) +- String refs (`ref="..."`): **0** +- `findDOMNode`: **0** +- Legacy context API (`childContextTypes` / `getChildContext`): **0** +- `useFormState`: **0** - Zero-arg `useRef()`: 0 - TS 5.9.2; `@types/react` 18.3.1 - Tests: Jest + `@testing-library/react` 16.3.0 - CI: `.github/workflows/{release,pr,main}.yml` — `npm publish` after transpile -## Recipe summary (full detail in master plan) +### Runbook insight: `react/19/migration-recipe` is destructive on this codebase + +Dry-running `react/19/migration-recipe` (codemod 1.9.1, which silently invokes legacy codemod 0.18.13) on backpack: + +- **Strips Apache 2.0 license headers** from every modified file. Backpack requires these. +- Generates **wrong TypeScript interfaces** when files already have proper TS types (e.g. `BpkBasicMapMarker.tsx` keeps the existing `type Props = { children: ReactNode, position: LatLong }` orphaned and points the component at a new redundant `interface { ...; position: unknown }`). +- Only matched **6 of 67** prop-types files in this repo despite the recipe including `prop-types-typescript`. + +We are skipping the bundled recipe. The four mechanical sub-codemods (`replace-act-import`, `replace-reactdom-render`, `replace-string-ref`, `replace-use-form-state`) all dry-run as 0-changes here, so they are also skipped. Only `types-react-codemod` (TS-types only, much narrower scope) is being run. + +The `prop-types` and `defaultProps` migrations are being done with a custom jscodeshift transform tailored to backpack, preserving license headers and faithful types. + +## Recipe summary — adapted for this repo 1. Pin `codemod` + `types-react-codemod` as devDeps with `--save-exact` (no `npx`/`pnpm dlx` — Skyscanner Security stance). -2. `./node_modules/.bin/codemod react/19/migration-recipe` -3. `./node_modules/.bin/types-react-codemod preset-19 .` -4. `./node_modules/.bin/types-react-codemod react-element-default-any-props .` -5. Manual `defaultProps` sweep (84 files — track per-file in PR description). -6. Manual review of the 1 string-ref/findDOMNode/legacy-context site and the 1 `ReactDOM.render` site. -7. `forwardRef` ref-callback implicit-return scan. -8. Widen `peerDependencies` in `packages/package.json` to `^17.0.2 || ^18.3.1 || ^19.2.5`. -9. Add CI matrix entry running tests against React 19.2.5. -10. Uninstall codemod packages. -11. Coordinate version bump with the Backpack team. +2. ~~`react/19/migration-recipe`~~ — **skipped** (destructive on this codebase; see runbook insight above). +3. ~~Individual mechanical codemods~~ — **skipped** (dry-runs as 0-changes for all four). +4. `./node_modules/.bin/types-react-codemod preset-19 .` +5. `./node_modules/.bin/types-react-codemod react-element-default-any-props .` +6. Apply custom jscodeshift transform for the 67 prop-types + 84 defaultProps files (preserves license headers; faithful types). +7. Manual review of any edge cases the transform missed. +8. `forwardRef` ref-callback implicit-return scan (14 files). +9. Widen `peerDependencies` in `packages/package.json` to `^17.0.2 || ^18.3.1 || ^19.2.5`. +10. Add CI matrix entry running tests against React 19.2.5. +11. Uninstall codemod packages. +12. Coordinate version bump with the Backpack team. ## Reference docs From 24e3939bdea1a47a95d549b76a161318070b1c68 Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Tue, 5 May 2026 17:03:36 +0100 Subject: [PATCH 03/11] [LOOM-2442] React 19: types-react-codemod preset-19 (filtered) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run preset-19 minus refobject-defaults and useRef-required-initial — those two emit code that only typechecks against @types/react@19, so defer to the future bump PR. 34 files. ReactElement -> ReactElement, scoped JSX imports, and deprecated-* type aliases replaced. License headers preserved. Manual fixups: array-type lint rule (3 sites: ReactElement[] -> Array>) and single-quote style on 2 new JSX imports. --- .../bpk-component-accordion/src/BpkAccordionItem.tsx | 2 +- .../src/withSingleItemAccordionState.tsx | 8 ++++---- .../bpk-component-ai-blurb/src/BpkAiBlurb-test.tsx | 2 +- .../src/accessibility-test.tsx | 2 +- .../src/BpkAriaLive.story-helpers.tsx | 4 ++-- packages/bpk-component-aria-live/src/BpkAriaLive.tsx | 2 +- .../src/BpkAutosuggestV2/BpkAutosuggest.stories.tsx | 12 ++++++------ .../src/BpkAutosuggestV2/BpkAutosuggest.tsx | 8 ++++---- .../src/BpkBottomSheet.tsx | 2 +- .../bpk-component-breakpoint/src/BpkBreakpoint.tsx | 8 ++++---- .../bpk-component-calendar/src/BpkCalendarWeek.tsx | 2 +- .../bpk-component-calendar/src/custom-proptypes.ts | 2 +- packages/bpk-component-card-list/src/common-types.ts | 8 ++++---- packages/bpk-component-card-list/testMocks.tsx | 2 ++ .../src/BpkCardV2/BpkCardV2-test.tsx | 2 +- .../src/BpkCardV2/accessibility-test.tsx | 2 +- .../src/BpkCardV2/integration-test.tsx | 2 +- .../src/BpkCardV2/snapshot-test.tsx | 2 +- .../src/BpkChatbotInput-test.tsx | 2 +- .../src/accessibility-test.tsx | 2 +- .../BpkComparisonTable/BpkComparisonTable-test.tsx | 2 +- .../src/BpkComparisonTable/accessibility-test.tsx | 2 +- .../src/BpkComparisonTray/BpkComparisonTray-test.tsx | 2 +- .../src/BpkComparisonTray/accessibility-test.tsx | 2 +- .../bpk-component-datepicker/src/BpkDatepicker.tsx | 2 +- .../src/BpkDrawerContent-test.tsx | 2 +- .../src/BpkFieldset.stories.tsx | 2 +- packages/bpk-component-fieldset/src/BpkFieldset.tsx | 2 +- packages/bpk-component-input/src/withOpenEvents.tsx | 2 +- packages/bpk-component-layout/src/BpkProvider.tsx | 2 +- .../src/BpkNavigationBar.tsx | 4 ++-- .../src/BpkNavigationTabGroup.tsx | 2 +- packages/bpk-component-select/src/BpkSelect.tsx | 2 +- packages/bpk-component-slider/src/BpkSlider.d.ts | 2 ++ 34 files changed, 55 insertions(+), 51 deletions(-) diff --git a/packages/bpk-component-accordion/src/BpkAccordionItem.tsx b/packages/bpk-component-accordion/src/BpkAccordionItem.tsx index 166d9ae657..74b959ffed 100644 --- a/packages/bpk-component-accordion/src/BpkAccordionItem.tsx +++ b/packages/bpk-component-accordion/src/BpkAccordionItem.tsx @@ -40,7 +40,7 @@ export type BpkAccordionItemProps = { className?: string; expanded?: boolean; initiallyExpanded?: boolean; - icon?: ReactElement; + icon?: ReactElement; onClick?: () => void; tagName?: 'span' | 'p' | 'text' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; textStyle?: (typeof TEXT_STYLES)[keyof typeof TEXT_STYLES]; diff --git a/packages/bpk-component-accordion/src/withSingleItemAccordionState.tsx b/packages/bpk-component-accordion/src/withSingleItemAccordionState.tsx index c89ffa3808..66189e05e4 100644 --- a/packages/bpk-component-accordion/src/withSingleItemAccordionState.tsx +++ b/packages/bpk-component-accordion/src/withSingleItemAccordionState.tsx @@ -24,10 +24,10 @@ import { wrapDisplayName } from '../../bpk-react-utils'; import type { BpkAccordionProps } from './BpkAccordion'; const getInitiallyExpanded = (children: ReactNode) => { - const accordionItems = Children.toArray(children) as ReactElement[]; + const accordionItems = Children.toArray(children) as Array>; const result = accordionItems.reduceRight( (prev, item) => (item.props.initiallyExpanded ? item : prev), - {} as ReactElement, + {} as ReactElement, ); return (result || {}).key || null; }; @@ -61,7 +61,7 @@ const withSingleItemAccordionState =

( this.setState({ expanded: key }); }; - renderAccordionItem = (accordionItem: ReactElement) => { + renderAccordionItem = (accordionItem: ReactElement) => { const expanded = this.state.expanded === accordionItem.key; const onClick = () => this.openAccordionItem(accordionItem?.key); @@ -74,7 +74,7 @@ const withSingleItemAccordionState =

( return ( {Children.toArray(children).map((el) => - this.renderAccordionItem(el as ReactElement), + this.renderAccordionItem(el as ReactElement), )} ); diff --git a/packages/bpk-component-ai-blurb/src/BpkAiBlurb-test.tsx b/packages/bpk-component-ai-blurb/src/BpkAiBlurb-test.tsx index f2b50100c7..6923e2e200 100644 --- a/packages/bpk-component-ai-blurb/src/BpkAiBlurb-test.tsx +++ b/packages/bpk-component-ai-blurb/src/BpkAiBlurb-test.tsx @@ -25,7 +25,7 @@ import { BpkProvider } from '../../bpk-component-layout'; import BpkAiBlurb from './BpkAiBlurb'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); describe('BpkAiBlurb.Root', () => { diff --git a/packages/bpk-component-ai-blurb/src/accessibility-test.tsx b/packages/bpk-component-ai-blurb/src/accessibility-test.tsx index 4f3c6373ef..9245677b02 100644 --- a/packages/bpk-component-ai-blurb/src/accessibility-test.tsx +++ b/packages/bpk-component-ai-blurb/src/accessibility-test.tsx @@ -26,7 +26,7 @@ import { BpkProvider } from '../../bpk-component-layout'; import BpkAiBlurb from './BpkAiBlurb'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); describe('BpkAiBlurb accessibility tests', () => { diff --git a/packages/bpk-component-aria-live/src/BpkAriaLive.story-helpers.tsx b/packages/bpk-component-aria-live/src/BpkAriaLive.story-helpers.tsx index 3ce2cd1f02..0c422fc071 100644 --- a/packages/bpk-component-aria-live/src/BpkAriaLive.story-helpers.tsx +++ b/packages/bpk-component-aria-live/src/BpkAriaLive.story-helpers.tsx @@ -28,8 +28,8 @@ import STYLES from './BpkAriaLive.stories.module.scss'; const getClassName = cssModules(STYLES); type AriaLiveDemoProps = { - preamble?: ReactElement | null; - children: ReactElement; + preamble?: ReactElement | null; + children: ReactElement; className?: string | null; style?: {}; visible?: Boolean; diff --git a/packages/bpk-component-aria-live/src/BpkAriaLive.tsx b/packages/bpk-component-aria-live/src/BpkAriaLive.tsx index fd8d57f8c0..3d38217bdb 100644 --- a/packages/bpk-component-aria-live/src/BpkAriaLive.tsx +++ b/packages/bpk-component-aria-live/src/BpkAriaLive.tsx @@ -34,7 +34,7 @@ export type PolitenessSetting = (typeof POLITENESS_SETTINGS)[keyof typeof POLITENESS_SETTINGS]; export type Props = { - children: ReactElement | string; + children: ReactElement | string; politenessSetting?: PolitenessSetting; visible?: boolean; className?: string | null; diff --git a/packages/bpk-component-autosuggest/src/BpkAutosuggestV2/BpkAutosuggest.stories.tsx b/packages/bpk-component-autosuggest/src/BpkAutosuggestV2/BpkAutosuggest.stories.tsx index 312fcb6152..06a69f0aab 100644 --- a/packages/bpk-component-autosuggest/src/BpkAutosuggestV2/BpkAutosuggest.stories.tsx +++ b/packages/bpk-component-autosuggest/src/BpkAutosuggestV2/BpkAutosuggest.stories.tsx @@ -17,7 +17,7 @@ */ import { Component } from 'react'; -import type { ReactElement, InputHTMLAttributes, LegacyRef } from 'react'; +import type { ReactElement, InputHTMLAttributes, Ref } from 'react'; import { userEvent, within } from 'storybook/test'; @@ -179,10 +179,10 @@ type Props = { multiSection: boolean; renderInputComponent?: ( inputProps: InputHTMLAttributes & { - ref?: LegacyRef; + ref?: Ref; }, - ) => ReactElement; - renderSectionTitle: (section: Section) => ReactElement | null; + ) => ReactElement; + renderSectionTitle: (section: Section) => ReactElement | null; getSectionSuggestions: (section: Section) => Suggestion[]; }; @@ -322,7 +322,7 @@ export const HighlightFistSuggestion: Story = { // --- Multi-section example --- -const renderSectionTitle = (section: { title: string }): ReactElement => ( +const renderSectionTitle = (section: { title: string }): ReactElement => (

{section.title}
); @@ -375,7 +375,7 @@ export const SmallInput: Story = { const renderCustomInput = ( inputProps: InputHTMLAttributes & { - ref?: LegacyRef; + ref?: Ref; }, ) => (
= { }) => void; onSuggestionsFetchRequested: (value: string) => void; onSuggestionsClearRequested: () => void; - renderSuggestion: (suggestion: T) => ReactElement; + renderSuggestion: (suggestion: T) => ReactElement; id: string; enterKeyHint?: EnterKeyHintType; getA11yResultsMessage: (resultCount: number) => string; @@ -113,17 +113,17 @@ export type BpkAutoSuggestProps = { isDesktop?: boolean; onLoad?: (inputValue: string) => void; onClick?: () => void; - renderBesideInput?: () => ReactElement; + renderBesideInput?: () => ReactElement; showClear?: boolean; theme?: Partial; highlightFirstSuggestion?: boolean; shouldRenderSuggestions?: (value?: string) => boolean; multiSection?: boolean; getSectionSuggestions?: (section: T) => T[]; - renderSectionTitle?: (section: T) => ReactElement | null; + renderSectionTitle?: (section: T) => ReactElement | null; alwaysRenderSuggestions?: boolean; onInputValueChange?: (input: { method: string; newValue: string }) => void; - renderInputComponent?: (inputProps: BpkInputRenderProps) => ReactElement; + renderInputComponent?: (inputProps: BpkInputRenderProps) => ReactElement; onSuggestionHighlighted?: (data: { suggestion: T | null }) => void; focusInputOnSuggestionClick?: boolean; }; diff --git a/packages/bpk-component-bottom-sheet/src/BpkBottomSheet.tsx b/packages/bpk-component-bottom-sheet/src/BpkBottomSheet.tsx index ef5fff6ae4..44395f4da9 100644 --- a/packages/bpk-component-bottom-sheet/src/BpkBottomSheet.tsx +++ b/packages/bpk-component-bottom-sheet/src/BpkBottomSheet.tsx @@ -169,7 +169,7 @@ const BpkBottomSheet = ({ // For custom title (ReactNode), wrap it with an element that has the correct id // so BpkNavigationBar's aria-labelledby reference is valid const titleWithId = hasTitle && typeof title !== 'string' && isValidElement(title) - ? cloneElement(title as ReactElement, { id: showHiddenTitle ? hiddenTitleId : headingId }) + ? cloneElement(title as ReactElement, { id: showHiddenTitle ? hiddenTitleId : headingId }) : title; return ( diff --git a/packages/bpk-component-breakpoint/src/BpkBreakpoint.tsx b/packages/bpk-component-breakpoint/src/BpkBreakpoint.tsx index 01bc0a52fe..af359b38c6 100644 --- a/packages/bpk-component-breakpoint/src/BpkBreakpoint.tsx +++ b/packages/bpk-component-breakpoint/src/BpkBreakpoint.tsx @@ -77,15 +77,15 @@ const BpkBreakpoint = ({ useLegacyWarning(query, legacy, isClient); if (isClient) { if (typeof children === 'function') { - return children(matches) as ReactElement; + return children(matches) as ReactElement; } - return matches ? (children as ReactElement) : null; + return matches ? (children as ReactElement) : null; } if (typeof children === 'function') { - return children(!!matchSSR) as ReactElement; + return children(!!matchSSR) as ReactElement; } - return matchSSR ? (children as ReactElement) : null; + return matchSSR ? (children as ReactElement) : null; }; export { BREAKPOINTS }; export default BpkBreakpoint; diff --git a/packages/bpk-component-calendar/src/BpkCalendarWeek.tsx b/packages/bpk-component-calendar/src/BpkCalendarWeek.tsx index 80cb2b57b1..8d1e346973 100644 --- a/packages/bpk-component-calendar/src/BpkCalendarWeek.tsx +++ b/packages/bpk-component-calendar/src/BpkCalendarWeek.tsx @@ -437,7 +437,7 @@ class BpkCalendarWeek extends Component { } type DateContainerProps = { - children: ReactElement; + children: ReactElement; className?: string | null; isEmptyCell: boolean; selectionType: string; diff --git a/packages/bpk-component-calendar/src/custom-proptypes.ts b/packages/bpk-component-calendar/src/custom-proptypes.ts index 01ae85d652..ab5ab79c39 100644 --- a/packages/bpk-component-calendar/src/custom-proptypes.ts +++ b/packages/bpk-component-calendar/src/custom-proptypes.ts @@ -49,4 +49,4 @@ export type WeekDay = { export type WeekDayKey = string; export type DaysOfWeek = WeekDay[]; export type DateModifiers = { [key: string]: Function }; -export type ReactComponent = string | ((props: any) => ReactElement); +export type ReactComponent = string | ((props: any) => ReactElement); diff --git a/packages/bpk-component-card-list/src/common-types.ts b/packages/bpk-component-card-list/src/common-types.ts index 09aad37575..1a21825d75 100644 --- a/packages/bpk-component-card-list/src/common-types.ts +++ b/packages/bpk-component-card-list/src/common-types.ts @@ -40,7 +40,7 @@ const ACCESSORY_MOBILE_TYPES = { } as const; type ExpandProps = { - children: string | ReactElement; + children: string | ReactElement; collapsed: boolean; onExpandToggle: () => void; }; @@ -54,7 +54,7 @@ type AccessibilityLabels = { }; type CardListBaseProps = { - cardList: ReactElement[]; + cardList: Array>; layoutMobile: LayoutMobile; layoutDesktop: LayoutDesktop; accessoryDesktop?: (typeof ACCESSORY_DESKTOP_TYPES)[keyof typeof ACCESSORY_DESKTOP_TYPES]; @@ -62,7 +62,7 @@ type CardListBaseProps = { initiallyShownCardsDesktop?: number; initiallyShownCardsMobile?: number; initiallyInViewCardIndex?: number; - chipGroup?: ReactElement; + chipGroup?: ReactElement; buttonContent?: React.ReactNode; onButtonClick?: () => void; onExpandClick?: () => void; @@ -80,7 +80,7 @@ type TitleProps = { } type CardListGridStackProps = { - children: ReactElement[]; + children: Array>; initiallyShownCards: number; layout: typeof LAYOUTS.grid | typeof LAYOUTS.stack; accessory?: diff --git a/packages/bpk-component-card-list/testMocks.tsx b/packages/bpk-component-card-list/testMocks.tsx index c336b05e78..84381c327e 100644 --- a/packages/bpk-component-card-list/testMocks.tsx +++ b/packages/bpk-component-card-list/testMocks.tsx @@ -16,6 +16,8 @@ * limitations under the License. */ +import type { JSX } from 'react'; + import BpkCard from '../bpk-component-card'; const mockCards = (numberOfCards: number): JSX.Element[] => diff --git a/packages/bpk-component-card/src/BpkCardV2/BpkCardV2-test.tsx b/packages/bpk-component-card/src/BpkCardV2/BpkCardV2-test.tsx index c33f26a582..b154fb5a5d 100644 --- a/packages/bpk-component-card/src/BpkCardV2/BpkCardV2-test.tsx +++ b/packages/bpk-component-card/src/BpkCardV2/BpkCardV2-test.tsx @@ -28,7 +28,7 @@ import { CARD_V2_SURFACE_COLORS, CARD_V2_VARIANTS } from './common-types'; const toKebab = (s: string) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`); -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); describe('BpkCardV2', () => { diff --git a/packages/bpk-component-card/src/BpkCardV2/accessibility-test.tsx b/packages/bpk-component-card/src/BpkCardV2/accessibility-test.tsx index 9c7d71fa48..c66ed72165 100644 --- a/packages/bpk-component-card/src/BpkCardV2/accessibility-test.tsx +++ b/packages/bpk-component-card/src/BpkCardV2/accessibility-test.tsx @@ -28,7 +28,7 @@ import { CARD_V2_SURFACE_COLORS, CARD_V2_VARIANTS } from './common-types'; expect.extend(toHaveNoViolations); -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); describe('BpkCardV2 Accessibility', () => { diff --git a/packages/bpk-component-card/src/BpkCardV2/integration-test.tsx b/packages/bpk-component-card/src/BpkCardV2/integration-test.tsx index 8dab0e17c2..637dd6cdbc 100644 --- a/packages/bpk-component-card/src/BpkCardV2/integration-test.tsx +++ b/packages/bpk-component-card/src/BpkCardV2/integration-test.tsx @@ -28,7 +28,7 @@ import { CARD_V2_SURFACE_COLORS } from './common-types'; const toKebab = (s: string) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`); -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); describe('BpkCardV2 Integration Tests', () => { diff --git a/packages/bpk-component-card/src/BpkCardV2/snapshot-test.tsx b/packages/bpk-component-card/src/BpkCardV2/snapshot-test.tsx index 69baec86a9..2b08f94b2d 100644 --- a/packages/bpk-component-card/src/BpkCardV2/snapshot-test.tsx +++ b/packages/bpk-component-card/src/BpkCardV2/snapshot-test.tsx @@ -27,7 +27,7 @@ import BpkText, { TEXT_STYLES } from '../../../bpk-component-text'; import BpkCardV2 from './BpkCardV2'; import { CARD_V2_SURFACE_COLORS, CARD_V2_VARIANTS } from './common-types'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); describe('BpkCardV2 Snapshots', () => { diff --git a/packages/bpk-component-chatbot-input/src/BpkChatbotInput-test.tsx b/packages/bpk-component-chatbot-input/src/BpkChatbotInput-test.tsx index 089663a76a..50875dff66 100644 --- a/packages/bpk-component-chatbot-input/src/BpkChatbotInput-test.tsx +++ b/packages/bpk-component-chatbot-input/src/BpkChatbotInput-test.tsx @@ -28,7 +28,7 @@ import BpkChatbotInput from './BpkChatbotInput'; import { CHATBOT_INPUT_TYPES } from './common-types'; import { MAX_CHARACTERS } from './constants'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); const defaultProps = { diff --git a/packages/bpk-component-chatbot-input/src/accessibility-test.tsx b/packages/bpk-component-chatbot-input/src/accessibility-test.tsx index 1fd78bd356..61761705f3 100644 --- a/packages/bpk-component-chatbot-input/src/accessibility-test.tsx +++ b/packages/bpk-component-chatbot-input/src/accessibility-test.tsx @@ -28,7 +28,7 @@ import { BpkProvider } from '../../bpk-component-layout'; import BpkChatbotInput from './BpkChatbotInput'; import { CHATBOT_INPUT_TYPES } from './common-types'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); const defaultProps = { diff --git a/packages/bpk-component-comparison-table/src/BpkComparisonTable/BpkComparisonTable-test.tsx b/packages/bpk-component-comparison-table/src/BpkComparisonTable/BpkComparisonTable-test.tsx index 491efce33a..89dc059342 100644 --- a/packages/bpk-component-comparison-table/src/BpkComparisonTable/BpkComparisonTable-test.tsx +++ b/packages/bpk-component-comparison-table/src/BpkComparisonTable/BpkComparisonTable-test.tsx @@ -27,7 +27,7 @@ import BpkComparisonTable from './BpkComparisonTable'; import type { BpkCompareColumn } from './common-types'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); beforeAll(() => { diff --git a/packages/bpk-component-comparison-table/src/BpkComparisonTable/accessibility-test.tsx b/packages/bpk-component-comparison-table/src/BpkComparisonTable/accessibility-test.tsx index 7b8c855eee..3206deceb7 100644 --- a/packages/bpk-component-comparison-table/src/BpkComparisonTable/accessibility-test.tsx +++ b/packages/bpk-component-comparison-table/src/BpkComparisonTable/accessibility-test.tsx @@ -27,7 +27,7 @@ import BpkComparisonTable from './BpkComparisonTable'; import type { BpkCompareColumn } from './common-types'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); expect.extend(toHaveNoViolations); diff --git a/packages/bpk-component-comparison-table/src/BpkComparisonTray/BpkComparisonTray-test.tsx b/packages/bpk-component-comparison-table/src/BpkComparisonTray/BpkComparisonTray-test.tsx index 78fb23e908..6f616389f4 100644 --- a/packages/bpk-component-comparison-table/src/BpkComparisonTray/BpkComparisonTray-test.tsx +++ b/packages/bpk-component-comparison-table/src/BpkComparisonTray/BpkComparisonTray-test.tsx @@ -26,7 +26,7 @@ import BpkComparisonTray from './BpkComparisonTray'; import type { BpkComparisonItem } from './common-types'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); const ITEM_1: BpkComparisonItem = { id: '1', label: 'VIP Cars', image: 'car1.png' }; diff --git a/packages/bpk-component-comparison-table/src/BpkComparisonTray/accessibility-test.tsx b/packages/bpk-component-comparison-table/src/BpkComparisonTray/accessibility-test.tsx index ef714dbb7a..42a76119fe 100644 --- a/packages/bpk-component-comparison-table/src/BpkComparisonTray/accessibility-test.tsx +++ b/packages/bpk-component-comparison-table/src/BpkComparisonTray/accessibility-test.tsx @@ -25,7 +25,7 @@ import { BpkProvider } from '../../../bpk-component-layout'; import BpkComparisonTray from './BpkComparisonTray'; -const renderWithProvider = (ui: ReactElement) => +const renderWithProvider = (ui: ReactElement) => render({ui}); expect.extend(toHaveNoViolations); diff --git a/packages/bpk-component-datepicker/src/BpkDatepicker.tsx b/packages/bpk-component-datepicker/src/BpkDatepicker.tsx index d561abfa9a..a31ded10a3 100644 --- a/packages/bpk-component-datepicker/src/BpkDatepicker.tsx +++ b/packages/bpk-component-datepicker/src/BpkDatepicker.tsx @@ -78,7 +78,7 @@ type Props = { /** * By default BpkInput. If passed, it should be a DOM node with a ref attached to it. */ - inputComponent: ReactElement | null; + inputComponent: ReactElement | null; dateModifiers?: {}; fixedWidth?: boolean; inputProps?: {}; diff --git a/packages/bpk-component-drawer/src/BpkDrawerContent-test.tsx b/packages/bpk-component-drawer/src/BpkDrawerContent-test.tsx index 731bff1461..1add107600 100644 --- a/packages/bpk-component-drawer/src/BpkDrawerContent-test.tsx +++ b/packages/bpk-component-drawer/src/BpkDrawerContent-test.tsx @@ -25,7 +25,7 @@ import BpkDrawerContent from './BpkDrawerContent'; jest.mock( 'react-transition-group/Transition', () => - ({ children }: { children: (state: string) => ReactElement }) => + ({ children }: { children: (state: string) => ReactElement }) => children('entered'), ); diff --git a/packages/bpk-component-fieldset/src/BpkFieldset.stories.tsx b/packages/bpk-component-fieldset/src/BpkFieldset.stories.tsx index 9999ffb760..6b3e73b257 100644 --- a/packages/bpk-component-fieldset/src/BpkFieldset.stories.tsx +++ b/packages/bpk-component-fieldset/src/BpkFieldset.stories.tsx @@ -95,7 +95,7 @@ type FieldsetContainerProps = { description?: string; required?: boolean; className?: string | null; - children: ReactElement; + children: ReactElement; }; type FieldsetContainerState = { diff --git a/packages/bpk-component-fieldset/src/BpkFieldset.tsx b/packages/bpk-component-fieldset/src/BpkFieldset.tsx index 6b2f2d2a6c..44bd45a810 100644 --- a/packages/bpk-component-fieldset/src/BpkFieldset.tsx +++ b/packages/bpk-component-fieldset/src/BpkFieldset.tsx @@ -29,7 +29,7 @@ import STYLES from './BpkFieldset.module.scss'; const getClassName = cssModules(STYLES); type BaseProps = { - children: ReactElement; + children: ReactElement; disabled?: boolean; valid?: boolean | null; required?: boolean; diff --git a/packages/bpk-component-input/src/withOpenEvents.tsx b/packages/bpk-component-input/src/withOpenEvents.tsx index 98b596b23b..ed0569d066 100644 --- a/packages/bpk-component-input/src/withOpenEvents.tsx +++ b/packages/bpk-component-input/src/withOpenEvents.tsx @@ -147,7 +147,7 @@ const withOpenEvents =

(WithOpenEventsInputComponent: Componen } }; - render(): ReactElement { + render(): ReactElement { const { className, hasTouchSupport, diff --git a/packages/bpk-component-layout/src/BpkProvider.tsx b/packages/bpk-component-layout/src/BpkProvider.tsx index 751fded8d4..19af275fcb 100644 --- a/packages/bpk-component-layout/src/BpkProvider.tsx +++ b/packages/bpk-component-layout/src/BpkProvider.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import type { ReactNode } from 'react'; +import type { ReactNode, JSX } from 'react'; import { useEffect, useState } from 'react'; import { LocaleProvider } from '@ark-ui/react'; diff --git a/packages/bpk-component-navigation-bar/src/BpkNavigationBar.tsx b/packages/bpk-component-navigation-bar/src/BpkNavigationBar.tsx index 98ac83d10a..90f4689466 100644 --- a/packages/bpk-component-navigation-bar/src/BpkNavigationBar.tsx +++ b/packages/bpk-component-navigation-bar/src/BpkNavigationBar.tsx @@ -42,8 +42,8 @@ export type Props = { * Note: this prop only applies when `title` is a string; ReactNode titles are not truncated and wrap naturally. */ wrapTitle?: boolean; className?: string; - leadingButton?: ReactElement | null; - trailingButton?: ReactElement | null; + leadingButton?: ReactElement | null; + trailingButton?: ReactElement | null; sticky?: boolean; barStyle?: BarStyle; [rest: string]: any; // Inexact rest. See decisions/inexact-rest.md diff --git a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx index 3c531b9559..e00a7265c5 100644 --- a/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx +++ b/packages/bpk-component-navigation-tab-group/src/BpkNavigationTabGroup.tsx @@ -61,7 +61,7 @@ type TabWrapProps = { tab: TabWrapItem; type: NavigationTabGroupTypes; selected: boolean; - children: ReactElement; + children: ReactElement; onClick: (e: MouseEvent) => void; }; diff --git a/packages/bpk-component-select/src/BpkSelect.tsx b/packages/bpk-component-select/src/BpkSelect.tsx index 1e47cb4ce1..d5105b21be 100644 --- a/packages/bpk-component-select/src/BpkSelect.tsx +++ b/packages/bpk-component-select/src/BpkSelect.tsx @@ -38,7 +38,7 @@ export type Props = Omit< dockedFirst?: boolean; dockedLast?: boolean; dockedMiddle?: boolean; - image?: ReactElement | null; + image?: ReactElement | null; large?: boolean; valid?: boolean | null; wrapperClassName?: string | null; diff --git a/packages/bpk-component-slider/src/BpkSlider.d.ts b/packages/bpk-component-slider/src/BpkSlider.d.ts index ddd1caf117..e482ec5946 100644 --- a/packages/bpk-component-slider/src/BpkSlider.d.ts +++ b/packages/bpk-component-slider/src/BpkSlider.d.ts @@ -15,6 +15,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import type { JSX } from 'react'; + import type { Props as BpkSliderProps } from './BpkSlider' export type Props = BpkSliderProps declare const BpkSlider: ({ ariaLabel, ariaValuetext, max, min, minDistance, onAfterChange, onChange, step, value }: Props) => JSX.Element From 2173faf45ab095a359280d8d24ec10c27cc51e96 Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Tue, 5 May 2026 22:24:47 +0100 Subject: [PATCH 04/11] [LOOM-2442] React 19: prop-types + defaultProps via custom transform Custom jscodeshift transform at scripts/react-19/transforms/strip-proptypes.js, applied in two passes: - .ts/.tsx (--parser=tsx): remove prop-types imports, .propTypes assignments, static propTypes class fields, AND migrate function-component .defaultProps into ES6 destructure defaults. - .js/.jsx (--parser=babylon): only the defaultProps migration. Keep prop-types in place; the project's react/prop-types rule wants validation and most .js components rely on prop-types. React 19 ignores propTypes silently so this is safe; full removal happens in the future TS migration. Skipped (manual follow-up): - 26 .tsx class components with static defaultProps (Phase C reports them). - ~38 implicit-return story arrow components with .defaultProps left in place. - Files where PropTypes is used as a const export (e.g. common-types.ts). Manual fixups: 1 eslint-disable for an unused destructured rest sibling (ignoreRestSiblings only allows when there's no default), and 2 dead-import cleanups in tsx files. License headers preserved everywhere. 27 files: +70 / -243. Lint 0 errors, typecheck clean, jest 172 tests pass on touched packages. --- .../src/BpkDarkExampleWrapper.js | 6 +- .../src/BpkAutosuggestSuggestion.js | 10 +- .../src/BpkBarchartBar.js | 24 +- .../src/BpkBarchartBars.js | 21 +- .../src/BpkChartAxis.js | 18 +- .../src/BpkChartDataTable.js | 6 +- .../src/BpkChartGridLines.js | 16 +- .../src/BpkCalendarGrid-test.tsx | 5 - .../bpk-component-chip/src/commonTypes.ts | 1 - .../src/BpkInput.stories.tsx | 9 - .../src/BpkBasicMapMarker.tsx | 8 +- .../bpk-component-map/src/BpkIconMarker.js | 9 +- .../src/BpkIconMarkerBackground.js | 8 +- packages/bpk-component-map/src/BpkMap.js | 45 +-- .../bpk-component-map/src/BpkMap.stories.js | 8 +- .../src/withGoogleMapsScript.js | 12 +- .../src/BpkPagination.js | 12 +- .../src/BpkPaginationBreak.js | 6 +- .../src/BpkPaginationNudger.js | 7 +- .../src/BpkPaginationPage.js | 6 +- .../src/BpkPhoneInput.js | 25 +- .../src/BpkScrollableCalendarGrid-test.tsx | 5 - .../BpkScrollableCalendarGridList-test.tsx | 5 - .../src/BpkSectionListItem.js | 9 +- .../src/BpkSectionListSection.js | 6 +- .../src/SpinnerLayout.story-helpers.tsx | 5 - .../bpk-component-ticket/src/BpkTicket.js | 21 +- .../react-19/transforms/strip-proptypes.js | 267 ++++++++++++++++++ 28 files changed, 337 insertions(+), 243 deletions(-) create mode 100644 scripts/react-19/transforms/strip-proptypes.js diff --git a/.storybook/bpk-storybook-utils/src/BpkDarkExampleWrapper.js b/.storybook/bpk-storybook-utils/src/BpkDarkExampleWrapper.js index c44d98ddf0..1e8e3eff17 100644 --- a/.storybook/bpk-storybook-utils/src/BpkDarkExampleWrapper.js +++ b/.storybook/bpk-storybook-utils/src/BpkDarkExampleWrapper.js @@ -24,7 +24,7 @@ import STYLES from './BpkDarkExampleWrapper.module.scss'; const getClassName = cssModules(STYLES); const BpkDarkExampleWrapper = (props: { padded: boolean }) => { - const { padded, ...rest } = props; + const { padded = false, ...rest } = props; return ( /* $FlowFixMe[cannot-spread-inexact] - inexact rest. See 'decisions/flowfixme.md'. */

{ ); }; -BpkDarkExampleWrapper.defaultProps = { - padded: false, -}; - export default BpkDarkExampleWrapper; diff --git a/packages/bpk-component-autosuggest/src/BpkAutosuggestSuggestion.js b/packages/bpk-component-autosuggest/src/BpkAutosuggestSuggestion.js index 6eccc8f130..d84ea169f2 100644 --- a/packages/bpk-component-autosuggest/src/BpkAutosuggestSuggestion.js +++ b/packages/bpk-component-autosuggest/src/BpkAutosuggestSuggestion.js @@ -38,7 +38,7 @@ type Props = { const BpkAutosuggestSuggestion = (props: Props) => { const classNames = [getClassName('bpk-autosuggest__suggestion')]; - const { className, icon, indent, subHeading, tertiaryLabel, value, ...rest } = + const { className = null, icon = null, indent = false, subHeading = null, tertiaryLabel = null, value, ...rest } = props; const Icon = icon; @@ -98,12 +98,4 @@ BpkAutosuggestSuggestion.propTypes = { className: PropTypes.string, }; -BpkAutosuggestSuggestion.defaultProps = { - subHeading: null, - tertiaryLabel: null, - icon: null, - indent: false, - className: null, -}; - export default BpkAutosuggestSuggestion; diff --git a/packages/bpk-component-barchart/src/BpkBarchartBar.js b/packages/bpk-component-barchart/src/BpkBarchartBar.js index 8116dcd8aa..306fcdd02a 100644 --- a/packages/bpk-component-barchart/src/BpkBarchartBar.js +++ b/packages/bpk-component-barchart/src/BpkBarchartBar.js @@ -61,15 +61,15 @@ type Props = { const BpkBarchartBar = (props: Props) => { const { - className, + className = null, height, label, - onClick, - onFocus, - onHover, - outlier, - padding, - selected, + onClick = null, + onFocus = null, + onHover = null, + outlier = false, + padding = 0, + selected = false, width, x, y, @@ -142,14 +142,4 @@ BpkBarchartBar.propTypes = { selected: PropTypes.bool, }; -BpkBarchartBar.defaultProps = { - className: null, - onClick: null, - onHover: null, - onFocus: null, - outlier: false, - padding: 0, - selected: false, -}; - export default BpkBarchartBar; diff --git a/packages/bpk-component-barchart/src/BpkBarchartBars.js b/packages/bpk-component-barchart/src/BpkBarchartBars.js index f3bc70372f..6c64f59113 100644 --- a/packages/bpk-component-barchart/src/BpkBarchartBars.js +++ b/packages/bpk-component-barchart/src/BpkBarchartBars.js @@ -76,15 +76,15 @@ const BpkBarchartBars = (props: Props) => { BarComponent, data, getBarLabel, - getBarSelection, + getBarSelection = () => false, height, - innerPadding, + innerPadding = 0.35, margin, maxYValue, - onBarClick, - onBarFocus, - onBarHover, - outerPadding, + onBarClick = null, + onBarFocus = null, + onBarHover = null, + outerPadding = 0.35, xScale, xScaleDataKey, yScale, @@ -154,13 +154,4 @@ BpkBarchartBars.propTypes = { onBarFocus: PropTypes.func, }; -BpkBarchartBars.defaultProps = { - outerPadding: 0.35, - innerPadding: 0.35, - onBarClick: null, - onBarHover: null, - onBarFocus: null, - getBarSelection: () => false, -}; - export default BpkBarchartBars; diff --git a/packages/bpk-component-barchart/src/BpkChartAxis.js b/packages/bpk-component-barchart/src/BpkChartAxis.js index 904dccd3fe..d39a836ad0 100644 --- a/packages/bpk-component-barchart/src/BpkChartAxis.js +++ b/packages/bpk-component-barchart/src/BpkChartAxis.js @@ -104,14 +104,14 @@ type Props = { const BpkChartAxis = (props: Props) => { const { height, - label, + label = null, margin, - numTicks, + numTicks = null, orientation, scale, - tickEvery, - tickOffset, - tickValue, + tickEvery = 1, + tickOffset = 0, + tickValue = identity, width, ...rest } = props; @@ -176,12 +176,4 @@ BpkChartAxis.propTypes = { tickEvery: PropTypes.number, }; -BpkChartAxis.defaultProps = { - tickOffset: 0, - tickEvery: 1, - tickValue: identity, - numTicks: null, - label: null, -}; - export default BpkChartAxis; diff --git a/packages/bpk-component-barchart/src/BpkChartDataTable.js b/packages/bpk-component-barchart/src/BpkChartDataTable.js index 4cc9a7bdab..0322397720 100644 --- a/packages/bpk-component-barchart/src/BpkChartDataTable.js +++ b/packages/bpk-component-barchart/src/BpkChartDataTable.js @@ -37,7 +37,7 @@ type Props = { }; const BpkChartDataTable = (props: Props) => { - const { data, xAxisLabel, xScaleDataKey, yAxisLabel, yScaleDataKey } = props; + const { data = null, xAxisLabel, xScaleDataKey, yAxisLabel, yScaleDataKey } = props; const rows = data.map((point, i) => { const key = `chart-data-table-row-${i}`; @@ -70,8 +70,4 @@ BpkChartDataTable.propTypes = { yAxisLabel: PropTypes.string.isRequired, }; -BpkChartDataTable.defaultProps = { - data: null, -}; - export default BpkChartDataTable; diff --git a/packages/bpk-component-barchart/src/BpkChartGridLines.js b/packages/bpk-component-barchart/src/BpkChartGridLines.js index 056a859b18..3092367536 100644 --- a/packages/bpk-component-barchart/src/BpkChartGridLines.js +++ b/packages/bpk-component-barchart/src/BpkChartGridLines.js @@ -49,11 +49,11 @@ const BpkChartGridLines = (props: Props) => { const { height, margin, - numTicks, + numTicks = null, orientation, scale, - tickEvery, - tickOffset, + tickEvery = 1, + tickOffset = 0, width, ...rest } = props; @@ -80,12 +80,12 @@ const BpkChartGridLines = (props: Props) => { const toLine = (tick, i) => ( // $FlowFixMe[cannot-spread-inexact] - inexact rest. See 'decisions/flowfixme.md'. - + />) ); return ( @@ -110,10 +110,4 @@ BpkChartGridLines.propTypes = { tickEvery: PropTypes.number, }; -BpkChartGridLines.defaultProps = { - numTicks: null, - tickOffset: 0, - tickEvery: 1, -}; - export default BpkChartGridLines; diff --git a/packages/bpk-component-calendar/src/BpkCalendarGrid-test.tsx b/packages/bpk-component-calendar/src/BpkCalendarGrid-test.tsx index f79649316d..e55b8ec885 100644 --- a/packages/bpk-component-calendar/src/BpkCalendarGrid-test.tsx +++ b/packages/bpk-component-calendar/src/BpkCalendarGrid-test.tsx @@ -16,8 +16,6 @@ * limitations under the License. */ -import PropTypes from 'prop-types'; - import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { addMonths, isWeekend, format } from 'date-fns'; @@ -83,9 +81,6 @@ describe('BpkCalendarGrid', () => { } return
; }; - MyCustomDate.propTypes = { - date: PropTypes.instanceOf(Date).isRequired, - }; const { asFragment } = render(
; class ClearableInput extends Component { - static propTypes = { - ...propTypes, - initialValue: PropTypes.string.isRequired, - }; - static defaultProps = { ...inputDefaultProps, }; diff --git a/packages/bpk-component-map/src/BpkBasicMapMarker.tsx b/packages/bpk-component-map/src/BpkBasicMapMarker.tsx index 88c1594d9d..ba93932c83 100644 --- a/packages/bpk-component-map/src/BpkBasicMapMarker.tsx +++ b/packages/bpk-component-map/src/BpkBasicMapMarker.tsx @@ -16,7 +16,6 @@ * limitations under the License. */ -import PropTypes from 'prop-types'; import type { ReactNode } from 'react'; import { getDataComponentAttribute } from '../../bpk-react-utils'; @@ -24,7 +23,7 @@ import { getDataComponentAttribute } from '../../bpk-react-utils'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. import BpkOverlayView from './BpkOverlayView'; // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. -import { LatLongPropType, type LatLong } from './common-types'; +import { type LatLong } from './common-types'; type Props = { children: ReactNode, @@ -51,9 +50,4 @@ const BpkBasicMapMarker = (props: Props) => { ); }; -BpkBasicMapMarker.propTypes = { - children: PropTypes.node.isRequired, - position: LatLongPropType.isRequired, -}; - export default BpkBasicMapMarker; diff --git a/packages/bpk-component-map/src/BpkIconMarker.js b/packages/bpk-component-map/src/BpkIconMarker.js index 16ea0f610e..cfc1c55525 100644 --- a/packages/bpk-component-map/src/BpkIconMarker.js +++ b/packages/bpk-component-map/src/BpkIconMarker.js @@ -41,7 +41,7 @@ export type Props = { }; const BpkIconMarker = (props: Props) => { - const { buttonProps, className, icon, onClick, position, selected, ...rest } = + const { buttonProps = null, className = null, icon, onClick = null, position, selected = false, ...rest } = props; const wrapperClassNames = getClassName( @@ -85,11 +85,4 @@ BpkIconMarker.propTypes = { buttonProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types }; -BpkIconMarker.defaultProps = { - className: null, - onClick: null, - selected: false, - buttonProps: null, -}; - export default BpkIconMarker; diff --git a/packages/bpk-component-map/src/BpkIconMarkerBackground.js b/packages/bpk-component-map/src/BpkIconMarkerBackground.js index d94b89b3ca..4cb53b1c85 100644 --- a/packages/bpk-component-map/src/BpkIconMarkerBackground.js +++ b/packages/bpk-component-map/src/BpkIconMarkerBackground.js @@ -31,7 +31,7 @@ type Props = { }; const BpkIconMarkerBackground = (props: Props) => { - const { disabled, interactive, selected, ...rest } = props; + const { disabled = false, interactive = false, selected = false, ...rest } = props; const classNames = getClassName( 'bpk-icon-marker-background', @@ -72,10 +72,4 @@ const BpkIconMarkerBackground = (props: Props) => { ); }; -BpkIconMarkerBackground.defaultProps = { - disabled: false, - interactive: false, - selected: false, -}; - export default BpkIconMarkerBackground; diff --git a/packages/bpk-component-map/src/BpkMap.js b/packages/bpk-component-map/src/BpkMap.js index 44000ecf2f..5511206bf2 100644 --- a/packages/bpk-component-map/src/BpkMap.js +++ b/packages/bpk-component-map/src/BpkMap.js @@ -78,20 +78,20 @@ type Props = { const BpkMap = (props: Props) => { const { - bounds, - center, - children, - className, - greedyGestureHandling, - mapId, - mapOptionStyles, - mapRef, - onRegionChange, - onTilesLoaded, - onZoom, - panEnabled, - showControls, - zoom, + bounds = null, + center = undefined, + children = null, + className = null, + greedyGestureHandling = false, + mapId = null, + mapOptionStyles = null, + mapRef = null, + onRegionChange = null, + onTilesLoaded = null, + onZoom = null, + panEnabled = true, + showControls = true, + zoom = 15, } = props; if (!bounds && !center) { @@ -210,21 +210,4 @@ BpkMap.propTypes = { mapId: PropTypes.string, }; -BpkMap.defaultProps = { - bounds: null, - center: undefined, - children: null, - greedyGestureHandling: false, - mapRef: null, - onRegionChange: null, - onZoom: null, - onTilesLoaded: null, - panEnabled: true, - showControls: true, - zoom: 15, - className: null, - mapOptionStyles: null, - mapId: null, -}; - export default BpkMap; diff --git a/packages/bpk-component-map/src/BpkMap.stories.js b/packages/bpk-component-map/src/BpkMap.stories.js index 80a1d4d1c1..bfafeb6325 100644 --- a/packages/bpk-component-map/src/BpkMap.stories.js +++ b/packages/bpk-component-map/src/BpkMap.stories.js @@ -97,7 +97,8 @@ const AlignedFoodIconSm = withRtlSupport(FoodIconSm); const AlignedHeartIconSm = withRtlSupport(HeartIconSm); const StoryMap = (props) => { - const { children, language, ...rest } = props; + // eslint-disable-next-line no-unused-vars + const { children = null, language = '', ...rest } = props; return (
{/* $FlowFixMe[cannot-spread-inexact] - inexact rest. See 'decisions/flowfixme.md'. */} @@ -113,11 +114,6 @@ StoryMap.propTypes = { language: PropTypes.string, }; -StoryMap.defaultProps = { - children: null, - language: '', -}; - const venues = [ { id: '1', diff --git a/packages/bpk-component-map/src/withGoogleMapsScript.js b/packages/bpk-component-map/src/withGoogleMapsScript.js index c6a1af0b89..d27c37b7b8 100644 --- a/packages/bpk-component-map/src/withGoogleMapsScript.js +++ b/packages/bpk-component-map/src/withGoogleMapsScript.js @@ -37,9 +37,9 @@ export const LibraryShapeType = PropTypes.arrayOf( function withGoogleMapsScript(Component: ComponentType) { const WithGoogleMapsScript = ({ googleMapsApiKey, - libraries, - loadingElement, - preventGoogleFontsLoading, + libraries = ['geometry', 'drawing', 'places'], + loadingElement = , + preventGoogleFontsLoading = false, ...rest }: { [string]: any, @@ -69,12 +69,6 @@ function withGoogleMapsScript(Component: ComponentType) { preventGoogleFontsLoading: PropTypes.bool, }; - WithGoogleMapsScript.defaultProps = { - loadingElement: , - preventGoogleFontsLoading: false, - libraries: ['geometry', 'drawing', 'places'], - }; - return WithGoogleMapsScript; } diff --git a/packages/bpk-component-pagination/src/BpkPagination.js b/packages/bpk-component-pagination/src/BpkPagination.js index 776e8c4164..5dfccaac11 100644 --- a/packages/bpk-component-pagination/src/BpkPagination.js +++ b/packages/bpk-component-pagination/src/BpkPagination.js @@ -36,15 +36,15 @@ const handlePageChange = (onPageChange, pageCount) => (nextPageIndex) => { const BpkPagination = (props) => { const classNames = [getClassName('bpk-pagination')]; const { - className, + className = null, nextLabel, - onPageChange, + onPageChange = null, pageCount, pageLabel, paginationLabel, previousLabel, selectedPageIndex, - visibleRange, + visibleRange = 3, ...rest } = props; @@ -93,10 +93,4 @@ BpkPagination.propTypes = { className: PropTypes.string, }; -BpkPagination.defaultProps = { - onPageChange: null, - visibleRange: 3, - className: null, -}; - export default BpkPagination; diff --git a/packages/bpk-component-pagination/src/BpkPaginationBreak.js b/packages/bpk-component-pagination/src/BpkPaginationBreak.js index 2c803e4e7f..f48f4263fa 100644 --- a/packages/bpk-component-pagination/src/BpkPaginationBreak.js +++ b/packages/bpk-component-pagination/src/BpkPaginationBreak.js @@ -19,7 +19,7 @@ import PropTypes from 'prop-types'; const BpkPaginationBreak = (props) => { - const { breakLabel } = props; + const { breakLabel = '...' } = props; return
{breakLabel}
; }; @@ -27,8 +27,4 @@ BpkPaginationBreak.propTypes = { breakLabel: PropTypes.string, }; -BpkPaginationBreak.defaultProps = { - breakLabel: '...', -}; - export default BpkPaginationBreak; diff --git a/packages/bpk-component-pagination/src/BpkPaginationNudger.js b/packages/bpk-component-pagination/src/BpkPaginationNudger.js index f79749ff14..32c60e32b4 100644 --- a/packages/bpk-component-pagination/src/BpkPaginationNudger.js +++ b/packages/bpk-component-pagination/src/BpkPaginationNudger.js @@ -34,7 +34,7 @@ const nudgerIcon = (forward) => forward ? () : (); const BpkPaginationNudger = (props) => { - const { disabled, forward, label, onNudge } = props; + const { disabled = false, forward = false, label, onNudge } = props; return (
@@ -60,9 +60,4 @@ BpkPaginationNudger.propTypes = { disabled: PropTypes.bool, }; -BpkPaginationNudger.defaultProps = { - forward: false, - disabled: false, -}; - export default BpkPaginationNudger; diff --git a/packages/bpk-component-pagination/src/BpkPaginationPage.js b/packages/bpk-component-pagination/src/BpkPaginationPage.js index c4f87d04f0..e6b2960462 100644 --- a/packages/bpk-component-pagination/src/BpkPaginationPage.js +++ b/packages/bpk-component-pagination/src/BpkPaginationPage.js @@ -26,7 +26,7 @@ const getClassName = cssModules(STYLES); const BpkPaginationPage = (props) => { const classNames = [getClassName('bpk-pagination-page')]; - const { isSelected, onSelect, page, pageLabel } = props; + const { isSelected = false, onSelect, page, pageLabel } = props; if (!isSelected) { // reverse class type so we can always load `buttons.bpk-button` as a base style for overridding. @@ -53,8 +53,4 @@ BpkPaginationPage.propTypes = { isSelected: PropTypes.bool, }; -BpkPaginationPage.defaultProps = { - isSelected: false, -}; - export default BpkPaginationPage; diff --git a/packages/bpk-component-phone-input/src/BpkPhoneInput.js b/packages/bpk-component-phone-input/src/BpkPhoneInput.js index b7f77ee393..c13f84fa28 100644 --- a/packages/bpk-component-phone-input/src/BpkPhoneInput.js +++ b/packages/bpk-component-phone-input/src/BpkPhoneInput.js @@ -75,21 +75,21 @@ type CommonProps = { const BpkPhoneInput = (props: Props) => { const { - className, + className = null, dialingCode, - dialingCodeMask, + dialingCodeMask = false, dialingCodeProps, dialingCodes, - disabled, + disabled = false, id, label, - large, + large = false, name, onChange, onDialingCodeChange, - valid, + valid = null, value, - wrapperProps, + wrapperProps = {}, ...rest } = props; @@ -170,9 +170,9 @@ const BpkPhoneInput = (props: Props) => { > {dialingCodes.map(({ code, description, ...extraDialingProps }) => ( // $FlowFixMe[cannot-spread-inexact] - inexact rest. See 'decisions/flowfixme.md'. - + ) ))}
@@ -229,13 +229,4 @@ BpkPhoneInput.propTypes = { wrapperProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types }; -BpkPhoneInput.defaultProps = { - className: null, - disabled: false, - dialingCodeMask: false, - large: false, - valid: null, - wrapperProps: {}, -}; - export default BpkPhoneInput; diff --git a/packages/bpk-component-scrollable-calendar/src/BpkScrollableCalendarGrid-test.tsx b/packages/bpk-component-scrollable-calendar/src/BpkScrollableCalendarGrid-test.tsx index 1842949eef..bb0b269dd6 100644 --- a/packages/bpk-component-scrollable-calendar/src/BpkScrollableCalendarGrid-test.tsx +++ b/packages/bpk-component-scrollable-calendar/src/BpkScrollableCalendarGrid-test.tsx @@ -16,8 +16,6 @@ * limitations under the License. */ -import PropTypes from 'prop-types'; - import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { addMonths, isWeekend } from 'date-fns'; @@ -93,9 +91,6 @@ describe('BpkCalendarScrollGrid', () => { } return
; }; - MyCustomDate.propTypes = { - date: PropTypes.instanceOf(Date).isRequired, - }; const { asFragment } = render( { } return
; }; - MyCustomDate.propTypes = { - date: PropTypes.instanceOf(Date).isRequired, - }; const { asFragment } = render( { - const { blank, children, className, href, onClick, ...rest } = props; + const { blank = false, children, className = null, href = null, onClick = null, ...rest } = props; const classNames = [ getClassName( 'bpk-section-list-item', @@ -103,11 +103,4 @@ BpkSectionListItem.propTypes = { onClick: PropTypes.func, }; -BpkSectionListItem.defaultProps = { - blank: false, - className: null, - href: null, - onClick: null, -}; - export default BpkSectionListItem; diff --git a/packages/bpk-component-section-list/src/BpkSectionListSection.js b/packages/bpk-component-section-list/src/BpkSectionListSection.js index a912075199..600737980f 100644 --- a/packages/bpk-component-section-list/src/BpkSectionListSection.js +++ b/packages/bpk-component-section-list/src/BpkSectionListSection.js @@ -35,7 +35,7 @@ type Props = { }; const BpkSectionListSection = (props: Props) => { - const { children, headerText, ...rest } = props; + const { children, headerText = null, ...rest } = props; return (
@@ -58,8 +58,4 @@ BpkSectionListSection.propTypes = { headerText: PropTypes.string, }; -BpkSectionListSection.defaultProps = { - headerText: null, -}; - export default BpkSectionListSection; diff --git a/packages/bpk-component-spinner/src/SpinnerLayout.story-helpers.tsx b/packages/bpk-component-spinner/src/SpinnerLayout.story-helpers.tsx index 937afb043a..52022fc6a6 100644 --- a/packages/bpk-component-spinner/src/SpinnerLayout.story-helpers.tsx +++ b/packages/bpk-component-spinner/src/SpinnerLayout.story-helpers.tsx @@ -16,7 +16,6 @@ * limitations under the License. */ -import PropTypes from 'prop-types'; import type { ReactElement } from 'react'; import { Children } from 'react'; @@ -49,8 +48,4 @@ const SpinnerLayout = (props: Props) => { ); }; -SpinnerLayout.propTypes = { - children: PropTypes.node.isRequired, -}; - export default SpinnerLayout; diff --git a/packages/bpk-component-ticket/src/BpkTicket.js b/packages/bpk-component-ticket/src/BpkTicket.js index 55a0f5f79d..b0bb72c1d6 100644 --- a/packages/bpk-component-ticket/src/BpkTicket.js +++ b/packages/bpk-component-ticket/src/BpkTicket.js @@ -46,13 +46,13 @@ type Props = { const BpkTicket = (props: Props) => { const { children, - className, - href, - padded, + className = null, + href = null, + padded = true, stub, - stubClassName, - stubProps, - vertical, + stubClassName = null, + stubProps = {}, + vertical = false, ...rest } = props; @@ -141,13 +141,4 @@ BpkTicket.propTypes = { stubProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types }; -BpkTicket.defaultProps = { - className: null, - href: null, - padded: true, - vertical: false, - stubClassName: null, - stubProps: {}, -}; - export default BpkTicket; diff --git a/scripts/react-19/transforms/strip-proptypes.js b/scripts/react-19/transforms/strip-proptypes.js new file mode 100644 index 0000000000..afbcdfad28 --- /dev/null +++ b/scripts/react-19/transforms/strip-proptypes.js @@ -0,0 +1,267 @@ +/* + * Strip prop-types and migrate function-component defaultProps for React 19. + * + * Phase A (TS/TSX files only): + * - Remove top-level `.propTypes = { ... }` ExpressionStatements. + * - Remove `static propTypes = { ... }` ClassProperty fields. + * - Remove `import PropTypes from 'prop-types'` IF no remaining references. + * + * Skipped for .js/.jsx because the project's eslint config requires either + * prop-types or types for prop validation, and most .js components rely on + * prop-types. React 19 ignores propTypes silently on all components, so + * leaving them in place doesn't break anything; the .js cleanup happens + * later as part of the TS migration. + * + * Phase B (all files): + * - Find `.defaultProps = ` ExpressionStatements + * where resolves to a function declaration / arrow function / + * function expression at the top level, AND the function's first parameter + * destructures `props` either inline or via `const { ... } = props` at the + * top of the body. + * - Merge each defaultProps key into the destructure as `key = value`. If the + * key isn't in the destructure already, append it. + * - Remove the .defaultProps assignment. + * - If the function uses `(props: Props)` and accesses props as `props.X` + * without a top-level destructure, SKIP — leave for manual review. + * + * Phase C (all files): + * - Class components with `static defaultProps`: SKIP and report. Manual + * migration required (convert to functional component or add destructure- + * with-defaults inside render()). + */ + +module.exports = function transformer(file, api) { + const j = api.jscodeshift; + const root = j(file.source); + let changed = false; + const skips = []; + + const runPhaseA = /\.(ts|tsx)$/.test(file.path); + + if (runPhaseA) { + // Phase A.2: remove `.propTypes = { ... }` ExpressionStatements. + root + .find(j.ExpressionStatement, { + expression: { + type: 'AssignmentExpression', + operator: '=', + left: { + type: 'MemberExpression', + property: { name: 'propTypes' }, + }, + }, + }) + .forEach((path) => { + const {left} = path.node.expression; + if (left.object.type !== 'Identifier') return; + j(path).remove(); + changed = true; + }); + + // Phase A.3: remove `static propTypes = { ... }` class fields. + root + .find(j.ClassProperty, { + static: true, + key: { name: 'propTypes' }, + }) + .forEach((path) => { + j(path).remove(); + changed = true; + }); + } + + // Phase B: function-component defaultProps → destructure defaults. + root + .find(j.ExpressionStatement, { + expression: { + type: 'AssignmentExpression', + operator: '=', + left: { + type: 'MemberExpression', + property: { name: 'defaultProps' }, + }, + right: { type: 'ObjectExpression' }, + }, + }) + .forEach((path) => { + const expr = path.node.expression; + const {left} = expr; + if (left.object.type !== 'Identifier') return; + const componentName = left.object.name; + const defaultsObj = expr.right; + + const decls = root + .find(j.VariableDeclarator, { id: { name: componentName } }) + .filter((p) => { + const {init} = p.node; + return ( + init && + (init.type === 'ArrowFunctionExpression' || + init.type === 'FunctionExpression') + ); + }); + + const fnDecls = root.find(j.FunctionDeclaration, { + id: { name: componentName }, + }); + + let fnPath = null; + if (decls.size() === 1) fnPath = decls.get(); + else if (fnDecls.size() === 1) fnPath = fnDecls.get(); + + if (!fnPath) { + skips.push(`${file.path}: defaultProps for "${componentName}" — declaration not found or ambiguous`); + return; + } + + const fnNode = fnPath.node.init || fnPath.node; + const {params} = fnNode; + if (!params || params.length === 0) { + skips.push(`${file.path}: defaultProps for "${componentName}" — fn has no params`); + return; + } + + const firstParam = params[0]; + let destructurePattern = null; + + if (firstParam.type === 'ObjectPattern') { + destructurePattern = firstParam; + } else if ( + firstParam.type === 'AssignmentPattern' && + firstParam.left.type === 'ObjectPattern' + ) { + destructurePattern = firstParam.left; + } else if (firstParam.type === 'Identifier') { + const paramName = firstParam.name; + const {body} = fnNode; + if (!body || body.type !== 'BlockStatement') { + skips.push(`${file.path}: defaultProps for "${componentName}" — body is not a block`); + return; + } + const firstStmt = body.body.find( + (s) => + s.type === 'VariableDeclaration' && + s.declarations.length === 1 && + s.declarations[0].id.type === 'ObjectPattern' && + s.declarations[0].init && + s.declarations[0].init.type === 'Identifier' && + s.declarations[0].init.name === paramName, + ); + if (!firstStmt) { + skips.push(`${file.path}: defaultProps for "${componentName}" — no top-level destructure of ${paramName}`); + return; + } + destructurePattern = firstStmt.declarations[0].id; + } else { + skips.push(`${file.path}: defaultProps for "${componentName}" — unhandled first param type ${firstParam.type}`); + return; + } + + for (const prop of defaultsObj.properties) { + if (prop.type !== 'Property' && prop.type !== 'ObjectProperty') { + skips.push(`${file.path}: defaultProps for "${componentName}" — non-Property entry, skipped`); + return; + } + if (prop.computed) { + skips.push(`${file.path}: defaultProps for "${componentName}" — computed key, skipped`); + return; + } + if (prop.key.type !== 'Identifier' && prop.key.type !== 'Literal' && prop.key.type !== 'StringLiteral') { + skips.push(`${file.path}: defaultProps for "${componentName}" — non-identifier key`); + return; + } + const keyName = + prop.key.type === 'Identifier' ? prop.key.name : prop.key.value; + + const existing = destructurePattern.properties.find((p) => { + if (p.type !== 'Property' && p.type !== 'ObjectProperty') return false; + if (p.computed) return false; + const k = p.key.type === 'Identifier' ? p.key.name : p.key.value; + return k === keyName; + }); + + if (existing) { + if (existing.value.type === 'AssignmentPattern') { + // Keep existing default; don't override. + } else { + existing.value = j.assignmentPattern(existing.value, prop.value); + changed = true; + } + } else { + const newProp = j.property.from({ + kind: 'init', + key: j.identifier(keyName), + value: j.assignmentPattern(j.identifier(keyName), prop.value), + shorthand: true, + }); + destructurePattern.properties.push(newProp); + changed = true; + } + } + + j(path).remove(); + changed = true; + }); + + if (runPhaseA) { + // Phase A.1: strip `import PropTypes from 'prop-types'` if no remaining + // references after A.2/A.3. Re-attach leading comments (license header) + // to the next sibling so they aren't lost with the import. + root + .find(j.ImportDeclaration, { source: { value: 'prop-types' } }) + .forEach((path) => { + const localName = path.node.specifiers + .map((s) => s.local && s.local.name) + .filter(Boolean)[0]; + if (!localName) { + j(path).remove(); + changed = true; + return; + } + const remainingRefs = root + .find(j.Identifier, { name: localName }) + .filter((p) => { + if ( + p.parent.node.type === 'ImportDefaultSpecifier' || + p.parent.node.type === 'ImportSpecifier' || + p.parent.node.type === 'ImportNamespaceSpecifier' + ) { + return false; + } + return true; + }); + if (remainingRefs.size() > 0) { + skips.push(`${file.path}: PropTypes still referenced — leaving import in place`); + return; + } + const parent = path.parent.node; + const siblings = parent.body || parent.program?.body; + if (siblings) { + const idx = siblings.indexOf(path.node); + const next = siblings[idx + 1]; + if (next && path.node.comments && path.node.comments.length > 0) { + const leading = path.node.comments.filter((c) => c.leading); + next.comments = (next.comments || []).concat(leading); + } + } + j(path).remove(); + changed = true; + }); + } + + // Phase C: report class components with `static defaultProps`. + root + .find(j.ClassProperty, { static: true, key: { name: 'defaultProps' } }) + .forEach(() => { + skips.push(`${file.path}: class component static defaultProps — manual migration`); + }); + + if (skips.length > 0) { + process.stderr.write(`${skips.map((s) => `[skip] ${s}`).join('\n') }\n`); + } + + return changed ? root.toSource({ quote: 'single' }) : null; +}; + +// Parser is selected via the `--parser` CLI flag. Run twice: once with +// --parser=tsx for .ts/.tsx, once with --parser=babylon for .js/.jsx. From d44401b3e6217e11da58f07e5ade38d8cdca4e2f Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Wed, 6 May 2026 09:01:52 +0100 Subject: [PATCH 05/11] [LOOM-2442] React 19: peerDep range, CI matrix, version-test widen packages/package.json: peerDep range narrowed-and-shifted from "17.0.2 - 18.3.1" to "18.3.1 - 19.2.5" (drops React 17, adds 19; lockfile updated). React 17 is unsupported upstream and has no consumers; dropping it shrinks the test/CI surface. .github/workflows/_build.yml: new React19 job runs typecheck + jest with react@19.2.5 and @types/react@19 installed --no-save into the cached node_modules. continue-on-error: true while the migration lands; flip to required once Repo 1 is released and consumers can opt in. packages/react-version-test.js: regex widened from /^18/ to /^(18|19)\./ so the runtime version test passes under both supported versions. REACT_19_MIGRATION.md: documents the peerDep decision + a runbook note on the drop-17 call vs the widen-to-3 default. Verified on React 18.3.1: typecheck clean, lint clean, check-react-versions + check-bpk-dependencies pass, full jest suite (2402 tests, 380 suites, 825 snapshots) passes. --- .github/workflows/_build.yml | 40 ++++++++++++++++++++++++++++++++++ REACT_19_MIGRATION.md | 8 +++++-- packages/package-lock.json | 4 ++-- packages/package.json | 4 ++-- packages/react-version-test.js | 4 ++-- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml index f48bb68b11..e998899837 100644 --- a/.github/workflows/_build.yml +++ b/.github/workflows/_build.yml @@ -66,6 +66,46 @@ jobs: name: ${{env.BUILD_LOGS}} path: ${{env.BUILD_LOGS}}.tar.br + React19: + # Forward-compatibility check: install React 19 + types into the cached + # node_modules (no save) and run typecheck + jest. Allowed to fail while + # the migration lands; flip to required once Repo 1 (backpack-web) is + # released and consumers can opt in. + runs-on: ubuntu-latest + needs: Build + continue-on-error: true + permissions: + statuses: write + pull-requests: write + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: '.nvmrc' + registry-url: 'https://registry.npmjs.org' + + - name: Restore Cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + id: npm-cache + with: + path: | + node_modules/ + packages/node_modules/ + key: ${{ env.CACHE_NAME }}-${{ hashFiles('package-lock.json', 'packages/package-lock.json') }} + + - name: Override React to 19.2.5 + run: npm install --no-save react@19.2.5 react-dom@19.2.5 @types/react@19 @types/react-dom@19 + + - name: Run typecheck (React 19) + run: npm run typecheck + + - name: Run jest (React 19) + run: TZ=Etc/UTC npm run jest + Danger: runs-on: ubuntu-latest needs: Build diff --git a/REACT_19_MIGRATION.md b/REACT_19_MIGRATION.md index 4a5dfd197e..0ba82f732a 100644 --- a/REACT_19_MIGRATION.md +++ b/REACT_19_MIGRATION.md @@ -16,7 +16,7 @@ This repo is **Repo 1 of 5** in the Skyscanner shared-library React 19 pre-relea The original baseline overstated several categories. Re-audit confirmed: - Lerna-style monorepo, published from `packages/package.json` -- React peerDep: `17.0.2 - 18.3.1` (must widen to `^17.0.2 || ^18.3.1 || ^19.2.5`) +- React peerDep: `17.0.2 - 18.3.1` → narrowed-and-shifted to `18.3.1 - 19.2.5` (We shouldn't have any consumers below 18) - `static defaultProps` / `defaultProps =`: **84 files** (manual sweep, no codemod) - `forwardRef`: 14 files (none combined with `defaultProps`) - `prop-types` imports: **67 files** (mostly `.js`; manual conversion — see runbook insight below) @@ -43,6 +43,10 @@ We are skipping the bundled recipe. The four mechanical sub-codemods (`replace-a The `prop-types` and `defaultProps` migrations are being done with a custom jscodeshift transform tailored to backpack, preserving license headers and faithful types. +### Runbook insight: peerDep range narrowed instead of widened + +Our intention is to **drop React 17 support** as part of this PR, to `18.3.1 - 19.2.5`. React 17 is unsupported upstream, no consumer is still on it, and dropping it shrinks the test/CI matrix surface. + ## Recipe summary — adapted for this repo 1. Pin `codemod` + `types-react-codemod` as devDeps with `--save-exact` (no `npx`/`pnpm dlx` — Skyscanner Security stance). @@ -53,7 +57,7 @@ The `prop-types` and `defaultProps` migrations are being done with a custom jsco 6. Apply custom jscodeshift transform for the 67 prop-types + 84 defaultProps files (preserves license headers; faithful types). 7. Manual review of any edge cases the transform missed. 8. `forwardRef` ref-callback implicit-return scan (14 files). -9. Widen `peerDependencies` in `packages/package.json` to `^17.0.2 || ^18.3.1 || ^19.2.5`. +9. Update `peerDependencies` in `packages/package.json` to `18.3.1 - 19.2.5` (drops React 17, adds 19; range syntax matching the existing peerDep style). 10. Add CI matrix entry running tests against React 19.2.5. 11. Uninstall codemod packages. 12. Coordinate version bump with the Backpack team. diff --git a/packages/package-lock.json b/packages/package-lock.json index cc9c56e3a1..8988d84e99 100644 --- a/packages/package-lock.json +++ b/packages/package-lock.json @@ -35,8 +35,8 @@ }, "peerDependencies": { "date-fns": "3.3.1 - 4", - "react": "17.0.2 - 18.3.1", - "react-dom": "17.0.2 - 18.3.1", + "react": "18.3.1 - 19.2.5", + "react-dom": "18.3.1 - 19.2.5", "react-transition-group": "^4.4.5", "sass": "^1", "sass-embedded": "^1" diff --git a/packages/package.json b/packages/package.json index 172053580a..0f91efba17 100644 --- a/packages/package.json +++ b/packages/package.json @@ -48,8 +48,8 @@ }, "peerDependencies": { "date-fns": "3.3.1 - 4", - "react": "17.0.2 - 18.3.1", - "react-dom": "17.0.2 - 18.3.1", + "react": "18.3.1 - 19.2.5", + "react-dom": "18.3.1 - 19.2.5", "react-transition-group": "^4.4.5", "sass": "^1", "sass-embedded": "^1" diff --git a/packages/react-version-test.js b/packages/react-version-test.js index 9d8d5f47d0..58d14d17e3 100644 --- a/packages/react-version-test.js +++ b/packages/react-version-test.js @@ -18,6 +18,6 @@ import { version } from 'react'; -it('packages/* should be ^18.0.0', () => { - expect(version).toMatch(/^18/); +it('packages/* should run on React 18 or 19', () => { + expect(version).toMatch(/^(18|19)\./); }); From 99ce9b462917093848dd0e8cfa0d19f58c93b5d5 Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Wed, 6 May 2026 13:49:23 +0100 Subject: [PATCH 06/11] [LOOM-2442] React 19: uninstall codemod packages + finalise docs Removes codemod@1.9.1 and types-react-codemod@3.5.3 from devDependencies (step 11 of the master plan recipe). The custom transform at scripts/react-19/transforms/strip-proptypes.js stays in the repo for reviewer traceability; scripts/react-19/README.md explains how to re-install jscodeshift on demand and notes the long-term home in web-migration-scripts/migrations/2026-05-react-19/. REACT_19_MIGRATION.md: recipe checklist marked complete; new "Deferred to follow-up PRs" section captures the 35 class-component static defaultProps migrations, 13 .js story/HOC files with leftover .defaultProps, full .js (Flow) prop-types removal, the @types/react@19 bump (which unlocks refobject-defaults + useRef-required-initial), the React19 CI matrix failure tracking, and moving the transform to web-migration-scripts. 2402 tests pass on React 18.3.1 after the uninstall. --- REACT_19_MIGRATION.md | 35 +- package-lock.json | 863 +------------------------------------ package.json | 2 - scripts/react-19/README.md | 74 ++++ 4 files changed, 117 insertions(+), 857 deletions(-) create mode 100644 scripts/react-19/README.md diff --git a/REACT_19_MIGRATION.md b/REACT_19_MIGRATION.md index 0ba82f732a..9dd59e9b8d 100644 --- a/REACT_19_MIGRATION.md +++ b/REACT_19_MIGRATION.md @@ -49,18 +49,29 @@ Our intention is to **drop React 17 support** as part of this PR, to `18.3.1 - 1 ## Recipe summary — adapted for this repo -1. Pin `codemod` + `types-react-codemod` as devDeps with `--save-exact` (no `npx`/`pnpm dlx` — Skyscanner Security stance). -2. ~~`react/19/migration-recipe`~~ — **skipped** (destructive on this codebase; see runbook insight above). -3. ~~Individual mechanical codemods~~ — **skipped** (dry-runs as 0-changes for all four). -4. `./node_modules/.bin/types-react-codemod preset-19 .` -5. `./node_modules/.bin/types-react-codemod react-element-default-any-props .` -6. Apply custom jscodeshift transform for the 67 prop-types + 84 defaultProps files (preserves license headers; faithful types). -7. Manual review of any edge cases the transform missed. -8. `forwardRef` ref-callback implicit-return scan (14 files). -9. Update `peerDependencies` in `packages/package.json` to `18.3.1 - 19.2.5` (drops React 17, adds 19; range syntax matching the existing peerDep style). -10. Add CI matrix entry running tests against React 19.2.5. -11. Uninstall codemod packages. -12. Coordinate version bump with the Backpack team. +1. [x] Pin `codemod` + `types-react-codemod` as devDeps with `--save-exact` (no `npx`/`pnpm dlx` — Skyscanner Security stance). +2. [x] ~~`react/19/migration-recipe`~~ — **skipped** (destructive on this codebase; see runbook insight above). +3. [x] ~~Individual mechanical codemods~~ — **skipped** (dry-runs as 0-changes for all four). +4. [x] `types-react-codemod preset-19` — applied with `refobject-defaults` and `useRef-required-initial` excluded (those two only typecheck against `@types/react@19`; deferred to the future `@types/react` bump PR). +5. [x] `types-react-codemod react-element-default-any-props` — confirmed no-op (already covered by preset-19). +6. [x] Custom jscodeshift transform applied (`scripts/react-19/transforms/strip-proptypes.js`, see scripts/react-19/README.md). Touched 27 files: removed prop-types from `.tsx`, migrated function-component `.defaultProps` to ES6 destructure defaults across `.js` and `.tsx`. License headers preserved. +7. [x] Manual cleanup of edge cases the transform left (1 eslint-disable + 2 dead-import removals). +8. [x] `forwardRef` ref-callback implicit-return scan — 0 issues, all 14 sites use block-body callbacks. +9. [x] Update `peerDependencies` in `packages/package.json` to `18.3.1 - 19.2.5` (drops React 17, adds 19; range syntax matching the existing peerDep style). +10. [x] Add CI matrix entry running tests against React 19.2.5 (`continue-on-error: true` initially). +11. [x] Uninstall codemod packages. +12. [ ] Coordinate version bump with the Backpack team. + +## Deferred to follow-up PRs + +The current PR establishes the scaffolding (peerDep range, CI matrix, codemod tooling, `.tsx` prop-types/defaultProps cleanup). The following items land in separate PRs: + +- **35 class components with `static defaultProps`** (27 `.tsx` + 8 `.js`) — React 19 makes these no-ops, so defaults silently stop applying. Each needs either conversion to a functional component (preferred) or destructure-with-defaults inside `render()` (quick fix). See the React19 CI matrix output for the failure surface; group by package to keep PRs reviewable. +- **13 `.js` story/HOC files with leftover `Component.defaultProps = ...`** — Phase B of the transform couldn't merge defaults because the function bodies are implicit-return arrows or otherwise ineligible. React 19 silently ignores these. +- **`.js` (Flow) prop-types removal** — intentionally skipped by the transform because the project's `react/prop-types` lint rule treats removed prop-types as missing prop validation on `.js` files. Full removal happens during the parallel TS migration; React 19 ignores `propTypes` silently in the meantime. +- **`@types/react@19` bump** — running `types-react-codemod preset-19`'s `refobject-defaults` and `useRef-required-initial` sub-transforms together with bumping `@types/react` to 19. These were skipped here because they emit code that only typechecks against `@types/react@19`. +- **Track and fix the React19 CI matrix failures** — typecheck has 8 known errors (the deferred sub-transforms above plus a missing `@types/prop-types`), and jest has 326 suite failures (mostly transitive deps still using removed React 18 internals like `ReactCurrentDispatcher`). Once green, flip the matrix from `continue-on-error: true` to required. +- **Move the custom transform to `web-migration-scripts/migrations/2026-05-react-19/transforms/`** when that migration directory is set up. ## Reference docs diff --git a/package-lock.json b/package-lock.json index 4e767ebafb..ebdb7e57f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,6 @@ "babel-plugin-module-resolver": "^5.0.0", "babel-plugin-react-docgen": "^4.2.1", "babel-plugin-require-context-hook": "^1.0.0", - "codemod": "1.9.1", "core-js": "^3.47.0", "css-loader": "^7.1.2", "d3-scale": "^4.0.2", @@ -83,7 +82,6 @@ "storybook": "^10.3.3", "style-loader": "^4.0.0", "ts-migrate": "^0.1.35", - "types-react-codemod": "3.5.3", "typescript": "^5.9.2", "webpack": "^5.103.0", "wrapper-webpack-plugin": "^2.2.2" @@ -2394,91 +2392,6 @@ "@keyv/serialize": "^1.1.1" } }, - "node_modules/@codemod.com/cli-darwin-arm64": { - "version": "1.9.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-darwin-arm64/-/cli-darwin-arm64-1.9.1.tgz", - "integrity": "sha512-WnSVAV9WC5sVUjc24a3FhaLXkdF78D0gofuKKhRpdcc7hloH6YH6lticiv0O4xE3ba4+URLiGjUUT55hn9cWAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@codemod.com/cli-darwin-x64": { - "version": "1.9.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-darwin-x64/-/cli-darwin-x64-1.9.1.tgz", - "integrity": "sha512-t8pWmKJgSbwwXR3D7Q36vemYkvI44lekl1hdr/Gw5ysKKi0BBg8VTotjopZmwn8AJ1uMqVG4YjMzzxrM8QdmyA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@codemod.com/cli-linux-arm64-gnu": { - "version": "1.9.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.9.1.tgz", - "integrity": "sha512-mpofqJUJS6QKgav+XctJvBJvgOHZndFcwzzG8NK176scEKPU/WaY20nc410RG06S1QPK1biH8UkPFn6cp98oeA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@codemod.com/cli-linux-x64-gnu": { - "version": "1.9.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.9.1.tgz", - "integrity": "sha512-xnV+PmkTMSFVGuxmQB7/BzJJVrMdTfgt75DVeXjAhrVzhvbNo6dlDdz6orRslPnA5t2C46NJqSQMjrZCzzUCBw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@codemod.com/cli-win32-x64-msvc": { - "version": "1.9.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@codemod.com/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.9.1.tgz", - "integrity": "sha512-VVsezk2HHLLYblPRnhu2p22WXDDg48lwIWItGNQ2sb2HJQTEaUbVv9f9ilRx9Rl0x0Q/70mQsT44n9iQ8f8XFg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@csstools/color-helpers": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", @@ -3857,480 +3770,37 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/ansi/-/ansi-1.0.2.tgz", - "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/checkbox": { - "version": "4.3.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/checkbox/-/checkbox-4.3.2.tgz", - "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/confirm": { - "version": "5.1.21", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/confirm/-/confirm-5.1.21.tgz", - "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "10.3.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/core/-/core-10.3.2.tgz", - "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@inquirer/core/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@inquirer/core/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@inquirer/core/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@inquirer/core/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@inquirer/core/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@inquirer/editor": { - "version": "4.2.23", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/editor/-/editor-4.2.23.tgz", - "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/external-editor": "^1.0.3", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/expand": { - "version": "4.0.23", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/expand/-/expand-4.0.23.tgz", - "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.15", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/figures/-/figures-1.0.15.tgz", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "4.3.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/input/-/input-4.3.1.tgz", - "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/number": { - "version": "3.0.23", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/number/-/number-3.0.23.tgz", - "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/password": { - "version": "4.0.23", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/password/-/password-4.0.23.tgz", - "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/prompts": { - "version": "7.10.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/prompts/-/prompts-7.10.1.tgz", - "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^4.3.2", - "@inquirer/confirm": "^5.1.21", - "@inquirer/editor": "^4.2.23", - "@inquirer/expand": "^4.0.23", - "@inquirer/input": "^4.3.1", - "@inquirer/number": "^3.0.23", - "@inquirer/password": "^4.0.23", - "@inquirer/rawlist": "^4.1.11", - "@inquirer/search": "^3.2.2", - "@inquirer/select": "^4.4.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.11", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/rawlist/-/rawlist-4.1.11.tgz", - "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } + "license": "BSD-3-Clause" }, - "node_modules/@inquirer/search": { - "version": "3.2.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/search/-/search-3.2.2.tgz", - "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, + "license": "Apache-2.0", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" + "node": ">=12.22" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@inquirer/select": { - "version": "4.4.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/select/-/select-4.4.2.tgz", - "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } + "license": "BSD-3-Clause" }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@inquirer/type/-/type-3.0.10.tgz", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" + "node": ">=18.18" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@isaacs/cliui": { @@ -10217,13 +9687,6 @@ "node": ">=10" } }, - "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "dev": true, - "license": "MIT" - }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -10448,16 +9911,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -10616,39 +10069,6 @@ "dev": true, "license": "MIT" }, - "node_modules/codemod": { - "version": "1.9.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/codemod/-/codemod-1.9.1.tgz", - "integrity": "sha512-Uff4wQGl9YgbBy5P3V65WqkkvgtJfXr5uFvMztDjyHdX66yiGZETukj7oHG1dlQV4sDEdP+jIThKyWSMz+1NOg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "bin": { - "codemod": "codemod" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "@codemod.com/cli-darwin-arm64": "1.9.1", - "@codemod.com/cli-darwin-x64": "1.9.1", - "@codemod.com/cli-linux-arm64-gnu": "1.9.1", - "@codemod.com/cli-linux-x64-gnu": "1.9.1", - "@codemod.com/cli-win32-x64-msvc": "1.9.1" - } - }, - "node_modules/codemod/node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, "node_modules/collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -16152,33 +15572,6 @@ "dev": true, "license": "ISC" }, - "node_modules/inquirer": { - "version": "12.11.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/inquirer/-/inquirer-12.11.1.tgz", - "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/prompts": "^7.10.1", - "@inquirer/type": "^3.0.10", - "mute-stream": "^2.0.0", - "run-async": "^4.0.6", - "rxjs": "^7.8.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -21239,16 +20632,6 @@ "node": ">= 10.13.0" } }, - "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/nano-spawn": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", @@ -24489,16 +23872,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-async": { - "version": "4.0.6", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/run-async/-/run-async-4.0.6.tgz", - "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -27623,16 +26996,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.14" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -28565,179 +27928,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/types-react-codemod": { - "version": "3.5.3", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/types-react-codemod/-/types-react-codemod-3.5.3.tgz", - "integrity": "sha512-3J4TMIicQL2G0UrQ0OF5ZmkWK5IVNUPbZ7GSzuKWMxB0SkGEJ+0XJnKnVR75l2g0kUxFugakh22/ptnbukPQuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.17.8", - "@babel/parser": "^7.17.8", - "@babel/preset-env": "^7.16.11", - "@babel/types": "^7.17.8", - "inquirer": "^12.0.0", - "jscodeshift": "^17.0.0", - "yargs": "^17.4.0" - }, - "bin": { - "types-react-codemod": "bin/types-react-codemod.cjs" - }, - "engines": { - "node": "18.x || 20.x || 22.x" - } - }, - "node_modules/types-react-codemod/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/types-react-codemod/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/types-react-codemod/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/types-react-codemod/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/types-react-codemod/node_modules/jscodeshift": { - "version": "17.3.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/jscodeshift/-/jscodeshift-17.3.0.tgz", - "integrity": "sha512-LjFrGOIORqXBU+jwfC9nbkjmQfFldtMIoS6d9z2LG/lkmyNXsJAySPT+2SWXJEoE68/bCWcxKpXH37npftgmow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/preset-flow": "^7.24.7", - "@babel/preset-typescript": "^7.24.7", - "@babel/register": "^7.24.6", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.7", - "neo-async": "^2.5.0", - "picocolors": "^1.0.1", - "recast": "^0.23.11", - "tmp": "^0.2.3", - "write-file-atomic": "^5.0.1" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - }, - "peerDependenciesMeta": { - "@babel/preset-env": { - "optional": true - } - } - }, - "node_modules/types-react-codemod/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/types-react-codemod/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/types-react-codemod/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/types-react-codemod/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -30464,19 +29654,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.3", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", - "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/zod": { "version": "3.25.58", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.58.tgz", diff --git a/package.json b/package.json index 925bead505..38e96da961 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,6 @@ "babel-plugin-module-resolver": "^5.0.0", "babel-plugin-react-docgen": "^4.2.1", "babel-plugin-require-context-hook": "^1.0.0", - "codemod": "1.9.1", "core-js": "^3.47.0", "css-loader": "^7.1.2", "d3-scale": "^4.0.2", @@ -183,7 +182,6 @@ "storybook": "^10.3.3", "style-loader": "^4.0.0", "ts-migrate": "^0.1.35", - "types-react-codemod": "3.5.3", "typescript": "^5.9.2", "webpack": "^5.103.0", "wrapper-webpack-plugin": "^2.2.2" diff --git a/scripts/react-19/README.md b/scripts/react-19/README.md new file mode 100644 index 0000000000..ba016bf9bb --- /dev/null +++ b/scripts/react-19/README.md @@ -0,0 +1,74 @@ +# React 19 migration scripts + +One-shot tooling used during the LOOM-2442 migration. Kept in the repo for +reproducibility; not part of the normal build/test pipeline. + +## `transforms/strip-proptypes.js` + +Custom jscodeshift transform that: + +- **Phase A** (`.ts`/`.tsx` only): removes `Identifier.propTypes = { ... }` + expression statements, `static propTypes` class fields, and the + `import PropTypes from 'prop-types'` import (only when no other references + to `PropTypes` remain in the file). +- **Phase B** (all files): converts function-component + `Identifier.defaultProps = ` into ES6 destructure + defaults at the function's parameter or top-of-body destructure point. +- **Phase C** (all files): reports class components with `static defaultProps` + to stderr — manual migration required (React 19 makes them no-ops). + +`.js`/`.jsx` files keep their `prop-types` in place because the project's +`react/prop-types` lint rule requires either prop-types or types for prop +validation, and most `.js` Flow components rely on prop-types. React 19 +ignores `propTypes` silently, so leaving them is safe; full removal will +happen during the parallel TS migration. + +## Running the transform + +The codemod toolchain isn't installed by default — install it on demand: + +```bash +npm install --save-dev --save-exact codemod@1.9.1 types-react-codemod@3.5.3 +``` + +(types-react-codemod transitively pulls in jscodeshift, which is what we +actually need.) + +Then run two passes: + +```bash +# .ts / .tsx files +node ./node_modules/types-react-codemod/node_modules/jscodeshift/bin/jscodeshift.js \ + --transform scripts/react-19/transforms/strip-proptypes.js \ + --parser=tsx \ + --no-babel \ + --extensions=ts,tsx \ + --ignore-pattern '**/{node_modules,dist,build,storybook-static}/**' \ + packages .storybook + +# .js / .jsx files (Flow) +node ./node_modules/types-react-codemod/node_modules/jscodeshift/bin/jscodeshift.js \ + --transform scripts/react-19/transforms/strip-proptypes.js \ + --parser=babylon \ + --no-babel \ + --extensions=js,jsx \ + --ignore-pattern '**/{node_modules,dist,build,storybook-static}/**' \ + packages .storybook +``` + +Skip messages (Phase B couldn't safely merge defaults; Phase C class +components; PropTypes-as-const-export files) go to stderr — capture with +`2>/tmp/skips.txt` for review. + +When done, uninstall the codemod packages: + +```bash +npm uninstall codemod types-react-codemod +``` + +## Future home + +This transform should ultimately move to +`/Users/SimonWard/Documents/Code/web-migration-scripts/migrations/2026-05-react-19/transforms/` +once that migration directory is set up. Kept locally for now to give +LOOM-2442 reviewers a clear lineage of how the diff was generated. From b26ec00f18db6c73d7436ce73af64c2881f638ec Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Wed, 6 May 2026 15:14:38 +0100 Subject: [PATCH 07/11] Removed local file refs --- REACT_19_MIGRATION.md | 9 +-------- scripts/react-19/README.md | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/REACT_19_MIGRATION.md b/REACT_19_MIGRATION.md index 9dd59e9b8d..956e2aff1e 100644 --- a/REACT_19_MIGRATION.md +++ b/REACT_19_MIGRATION.md @@ -1,8 +1,6 @@ # React 19 Migration — `@skyscanner/backpack-web` -This repo is **Repo 1 of 5** in the Skyscanner shared-library React 19 pre-release codemod pass. - -**Master plan**: [`/Users/SimonWard/Documents/Code/legal-pages/REACT_19_SHARED_LIBRARY_MIGRATION.md`](../legal-pages/REACT_19_SHARED_LIBRARY_MIGRATION.md) — see the **`Repo 1 — @skyscanner/backpack-web`** section. +(This repo is **Repo 1 of 5** in the Skyscanner shared-library React 19 pre-release codemod pass.) ## What this thread should do @@ -73,8 +71,3 @@ The current PR establishes the scaffolding (peerDep range, CI matrix, codemod to - **Track and fix the React19 CI matrix failures** — typecheck has 8 known errors (the deferred sub-transforms above plus a missing `@types/prop-types`), and jest has 326 suite failures (mostly transitive deps still using removed React 18 internals like `ReactCurrentDispatcher`). Once green, flip the matrix from `continue-on-error: true` to required. - **Move the custom transform to `web-migration-scripts/migrations/2026-05-react-19/transforms/`** when that migration directory is set up. -## Reference docs - -- [`/Users/SimonWard/Documents/Code/legal-pages/REACT_19_UPGRADE_GUIDE.md`](../legal-pages/REACT_19_UPGRADE_GUIDE.md) — codemod commands, manual migration patterns, troubleshooting -- [`/Users/SimonWard/Documents/Code/legal-pages/REACT_19_MIGRATION_REQUIREMENTS.md`](../legal-pages/REACT_19_MIGRATION_REQUIREMENTS.md) — peer-dep audit and breaking-changes inventory -- `/Users/SimonWard/Documents/Code/web-migration-scripts/migrations/2024-04-react-18_3_1/` — structural template for the migration directory we'll build at `2026-05-react-19/` diff --git a/scripts/react-19/README.md b/scripts/react-19/README.md index ba016bf9bb..f951a69212 100644 --- a/scripts/react-19/README.md +++ b/scripts/react-19/README.md @@ -69,6 +69,6 @@ npm uninstall codemod types-react-codemod ## Future home This transform should ultimately move to -`/Users/SimonWard/Documents/Code/web-migration-scripts/migrations/2026-05-react-19/transforms/` +`web-migration-scripts/migrations/2026-05-react-19/transforms/` once that migration directory is set up. Kept locally for now to give LOOM-2442 reviewers a clear lineage of how the diff was generated. From 908cb028783b626bb54c9b2bc6ade1a35aa3c700 Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Wed, 6 May 2026 16:02:57 +0100 Subject: [PATCH 08/11] fixed registry urls in lockfiles --- package-lock.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6c1ed2924..bb4db39578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -502,7 +502,7 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.28.6", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", @@ -881,7 +881,7 @@ }, "node_modules/@babel/plugin-syntax-flow": { "version": "7.28.6", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", "dev": true, "license": "MIT", @@ -1394,7 +1394,7 @@ }, "node_modules/@babel/plugin-transform-flow-strip-types": { "version": "7.27.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", "dev": true, "license": "MIT", @@ -2174,7 +2174,7 @@ }, "node_modules/@babel/preset-flow": { "version": "7.27.1", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/@babel/preset-flow/-/preset-flow-7.27.1.tgz", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.27.1.tgz", "integrity": "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==", "dev": true, "license": "MIT", @@ -23924,7 +23924,7 @@ }, "node_modules/rxjs": { "version": "7.8.2", - "resolved": "https://artifactory.skyscannertools.net/artifactory/api/npm/npm/rxjs/-/rxjs-7.8.2.tgz", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", From dd11d0d2ba7fb2c4a5a1fd2448161e6ab016cd14 Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Wed, 6 May 2026 17:04:11 +0100 Subject: [PATCH 09/11] fix to react19 step build gulp --- .github/workflows/_build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml index e998899837..af4742c0c2 100644 --- a/.github/workflows/_build.yml +++ b/.github/workflows/_build.yml @@ -100,6 +100,9 @@ jobs: - name: Override React to 19.2.5 run: npm install --no-save react@19.2.5 react-dom@19.2.5 @types/react@19 @types/react-dom@19 + - name: Generate component code (icons, flare, spinners) + run: npm run build:gulp + - name: Run typecheck (React 19) run: npm run typecheck From 394633710219593149aa870ced85b278cf12161b Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Wed, 6 May 2026 18:05:43 +0100 Subject: [PATCH 10/11] =?UTF-8?q?Refactor=20ref=20types=20to=20be=20R18/R1?= =?UTF-8?q?9=20compatible=20(RefObject=20=E2=86=92=20Ref=20or=20=20?= =?UTF-8?q?=20RefObject),=20add=20@types/prop-types=20to=20t?= =?UTF-8?q?he=20override=20install=20=20=20(R19's=20@types/react=20no=20lo?= =?UTF-8?q?nger=20pulls=20it=20in=20transitively),=20and=20overlay=20=20?= =?UTF-8?q?=20the=20React=2019=20install=20into=20packages/node=5Fmodules?= =?UTF-8?q?=20so=20jest=20sees=20a=20single=20=20=20React=20version=20acro?= =?UTF-8?q?ss=20both=20module=20trees.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remaining R19 jest failures (continue-on-error: true) are react-transition-group findDOMNode usage and useId() snapshot format drift — separate migration work. --- .github/workflows/_build.yml | 9 ++++++++- .../bpk-component-chatbot-input/src/ChatDemo.stories.tsx | 2 +- .../src/hooks/useChatbotInput.ts | 2 +- .../src/hooks/useTextAreaAutoResize.ts | 2 +- packages/bpk-component-datepicker/src/BpkDatepicker.tsx | 4 ++-- packages/bpk-component-drawer/src/BpkDrawerContent.tsx | 4 ++-- packages/bpk-component-slider/src/BpkSlider.tsx | 6 +++--- 7 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/_build.yml b/.github/workflows/_build.yml index af4742c0c2..3ba1605965 100644 --- a/.github/workflows/_build.yml +++ b/.github/workflows/_build.yml @@ -98,7 +98,14 @@ jobs: key: ${{ env.CACHE_NAME }}-${{ hashFiles('package-lock.json', 'packages/package-lock.json') }} - name: Override React to 19.2.5 - run: npm install --no-save react@19.2.5 react-dom@19.2.5 @types/react@19 @types/react-dom@19 + # The inner packages/node_modules tree has its own copy of react/react-dom + # populated from the cache. Overlay the root install into it so jest + # (which resolves react-dom from packages/) sees R19 too. + run: | + npm install --no-save react@19.2.5 react-dom@19.2.5 @types/react@19 @types/react-dom@19 @types/prop-types + rm -rf packages/node_modules/react packages/node_modules/react-dom + cp -r node_modules/react packages/node_modules/ + cp -r node_modules/react-dom packages/node_modules/ - name: Generate component code (icons, flare, spinners) run: npm run build:gulp diff --git a/packages/bpk-component-chatbot-input/src/ChatDemo.stories.tsx b/packages/bpk-component-chatbot-input/src/ChatDemo.stories.tsx index 5ca56823e1..4cff7d10f1 100644 --- a/packages/bpk-component-chatbot-input/src/ChatDemo.stories.tsx +++ b/packages/bpk-component-chatbot-input/src/ChatDemo.stories.tsx @@ -298,7 +298,7 @@ const ChatShell = ({ }: { inputSection: ReactNode; messages: Message[]; - messagesEndRef: React.RefObject; + messagesEndRef: React.Ref; renderMessage: (message: Message) => ReactNode; }) => ( ; + inputRef: RefObject; isFocused: boolean; isDisabled: boolean; isOverLimit: boolean; diff --git a/packages/bpk-component-chatbot-input/src/hooks/useTextAreaAutoResize.ts b/packages/bpk-component-chatbot-input/src/hooks/useTextAreaAutoResize.ts index 7679481cbd..3f86388d5e 100644 --- a/packages/bpk-component-chatbot-input/src/hooks/useTextAreaAutoResize.ts +++ b/packages/bpk-component-chatbot-input/src/hooks/useTextAreaAutoResize.ts @@ -20,7 +20,7 @@ import { useCallback, useLayoutEffect, useRef, useState } from 'react'; import type { RefObject } from 'react'; interface UseTextAreaAutoResizeProps { - ref: RefObject; + ref: RefObject; value: string; enabled?: boolean; maxLines?: number; diff --git a/packages/bpk-component-datepicker/src/BpkDatepicker.tsx b/packages/bpk-component-datepicker/src/BpkDatepicker.tsx index a31ded10a3..77888c9c82 100644 --- a/packages/bpk-component-datepicker/src/BpkDatepicker.tsx +++ b/packages/bpk-component-datepicker/src/BpkDatepicker.tsx @@ -17,7 +17,7 @@ */ import { createRef, Component } from 'react'; -import type { ReactElement, RefObject } from 'react'; +import type { ReactElement, Ref } from 'react'; import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint'; import { @@ -113,7 +113,7 @@ class BpkDatepicker extends Component { elementRef?: HTMLInputElement; - focusRef?: RefObject; + focusRef?: Ref; static defaultProps = { calendarComponent: DefaultCalendar, diff --git a/packages/bpk-component-drawer/src/BpkDrawerContent.tsx b/packages/bpk-component-drawer/src/BpkDrawerContent.tsx index 0561606c82..651685f95c 100644 --- a/packages/bpk-component-drawer/src/BpkDrawerContent.tsx +++ b/packages/bpk-component-drawer/src/BpkDrawerContent.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import type { CSSProperties, ReactNode, RefObject } from 'react'; +import type { CSSProperties, ReactNode, Ref } from 'react'; import { Transition } from 'react-transition-group'; @@ -33,7 +33,7 @@ const getClassName = cssModules(STYLES); type Props = { children: ReactNode, - dialogRef: () => RefObject, + dialogRef: Ref, onCloseAnimationComplete: () => void, onClose: () => void id: string, diff --git a/packages/bpk-component-slider/src/BpkSlider.tsx b/packages/bpk-component-slider/src/BpkSlider.tsx index 79c1cd724a..99430d60b9 100644 --- a/packages/bpk-component-slider/src/BpkSlider.tsx +++ b/packages/bpk-component-slider/src/BpkSlider.tsx @@ -102,7 +102,7 @@ const BpkSlider = ({ } }, []); - const thumbRefs = [useRef(null), useRef(null)]; + const thumbRefs = [useRef(null), useRef(null)]; const handleOnChange = useCallback( (sliderValues: number[]) => { @@ -201,12 +201,12 @@ const BpkSlider = ({ const BubbleInput = forwardRef( ( props: ComponentPropsWithRef<'input'> & { - thumbRef: RefObject; + thumbRef: RefObject; }, forwardedRef, ) => { const { thumbRef, value, ...inputProps } = props; - const ref = useRef(); + const ref = useRef(null); const composedRefs = useComposedRefs(forwardedRef, ref); // This Hook Provides the native behaviour that the input range type would have around the "change" event. From 0bd4e9d77e815d9ce339d87263dc3e0f9fa172be Mon Sep 17 00:00:00 2001 From: Simon Ward Date: Wed, 6 May 2026 19:15:47 +0100 Subject: [PATCH 11/11] =?UTF-8?q?New=20scripts/jest/normalizeUseIdSerializ?= =?UTF-8?q?er.js=20=E2=80=94=20wraps=20the=20default=20DOM=20serializer=20?= =?UTF-8?q?and=20rewrites=20R19's=20=5Fr=5FX=5F=20useId=20format=20back=20?= =?UTF-8?q?to=20R18's=20:rX:=20so=20a=20single=20set=20of=20snapshots=20se?= =?UTF-8?q?rves=20both=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++ scripts/jest/normalizeUseIdSerializer.js | 43 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 scripts/jest/normalizeUseIdSerializer.js diff --git a/package.json b/package.json index e8b0695d8a..95af867f3e 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,9 @@ "setupFilesAfterEnv": [ "/scripts/jest/setup.js" ], + "snapshotSerializers": [ + "/scripts/jest/normalizeUseIdSerializer.js" + ], "testEnvironment": "jsdom", "testRegex": "(?:packages|token-sync)/.*-test\\.[jt]sx?$", "transformIgnorePatterns": [ diff --git a/scripts/jest/normalizeUseIdSerializer.js b/scripts/jest/normalizeUseIdSerializer.js new file mode 100644 index 0000000000..fd1dc3ecba --- /dev/null +++ b/scripts/jest/normalizeUseIdSerializer.js @@ -0,0 +1,43 @@ +/* + * Backpack - Skyscanner's Design System + * + * Copyright 2016 Skyscanner Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// React 18's useId() emits IDs of the form `:r{n}:` while React 19 emits +// `_r_{n}_`. Tests that snapshot DOM with these IDs would otherwise need two +// sets of snapshots — one per React major. This serializer normalises the R19 +// form back to the R18 form so a single snapshot serves both. +const { plugins } = require('pretty-format'); + +const { DOMCollection, DOMElement } = plugins; +const r19Pattern = /_r_(\w+)_/g; + +module.exports = { + test: (val) => + val != null && (DOMElement.test(val) || DOMCollection.test(val)), + serialize: (val, config, indentation, depth, refs, printer) => { + const inner = DOMElement.test(val) ? DOMElement : DOMCollection; + const serialized = inner.serialize( + val, + config, + indentation, + depth, + refs, + printer, + ); + return serialized.replace(r19Pattern, ':r$1:'); + }, +};