diff --git a/.circleci/config.yml b/.circleci/config.yml index 6f545dc011d12..47b6ee5a60167 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,8 +5,8 @@ parameters: description: Whether to force browserstack usage. We have limited resources on browserstack so the pipeline might decide to skip browserstack if this parameter isn't set to true. type: boolean default: false - react-dist-tag: - description: The dist-tag of react to be used + react-version: + description: The version of react to be used type: string default: stable workflow: @@ -20,10 +20,10 @@ parameters: default-job: &default-job parameters: - react-dist-tag: - description: The dist-tag of react to be used + react-version: + description: The version of react to be used type: string - default: << pipeline.parameters.react-dist-tag >> + default: << pipeline.parameters.react-version >> e2e-base-url: description: The base url for running end-to-end test type: string @@ -33,7 +33,7 @@ default-job: &default-job PLAYWRIGHT_BROWSERS_PATH: /tmp/pw-browsers # expose it globally otherwise we have to thread it from each job to the install command BROWSERSTACK_FORCE: << pipeline.parameters.browserstack-force >> - REACT_DIST_TAG: << parameters.react-dist-tag >> + REACT_VERSION: << parameters.react-version >> working_directory: /tmp/mui docker: - image: cimg/node:18.20 @@ -59,6 +59,13 @@ commands: description: 'Set to true if you intend to any browser (for example with playwright).' steps: + - run: + name: Resolve React version + command: | + node scripts/useReactVersion.mjs + # log a patch for maintainers who want to check out this change + git --no-pager diff HEAD + - when: condition: << parameters.browsers >> steps: @@ -112,18 +119,23 @@ jobs: steps: - checkout - install_js - - run: - name: Should not have any git not staged - command: git add -A && git diff --exit-code --staged - - run: - name: Check for duplicated packages - command: | - if [[ $(git diff --name-status next | grep pnpm-lock) == "" ]]; - then - echo "No changes to dependencies detected. Skipping..." - else - pnpm dedupe --check - fi + - when: + # Install can be "dirty" when running with non-default versions of React + condition: + equal: [<< parameters.react-version >>, stable] + steps: + - run: + name: Should not have any git not staged + command: git add -A && git diff --exit-code --staged + - run: + name: Check for duplicated packages + command: | + if [[ $(git diff --name-status next | grep pnpm-lock) == "" ]]; + then + echo "No changes to dependencies detected. Skipping..." + else + pnpm dedupe --check + fi test_unit: <<: *default-job steps: @@ -147,7 +159,7 @@ jobs: command: | curl -Os https://uploader.codecov.io/latest/linux/codecov chmod +x codecov - ./codecov -t ${CODECOV_TOKEN} -Z -F "$REACT_DIST_TAG-jsdom" + ./codecov -t ${CODECOV_TOKEN} -Z -F "$REACT_VERSION-jsdom" test_lint: <<: *default-job steps: @@ -337,3 +349,25 @@ workflows: - test_e2e_website: requires: - checkout + + react-next: + # triggers: + # - schedule: + # cron: '0 0 * * *' + # filters: + # branches: + # only: + # - master + jobs: + - test_unit: + <<: *default-context + react-version: next + - test_browser: + <<: *default-context + react-version: next + - test_regressions: + <<: *default-context + react-version: next + - test_e2e: + <<: *default-context + react-version: next diff --git a/scripts/useReactVersion.mjs b/scripts/useReactVersion.mjs new file mode 100644 index 0000000000000..479634e6241a5 --- /dev/null +++ b/scripts/useReactVersion.mjs @@ -0,0 +1,112 @@ +/* eslint-disable no-console */ +/** + * Given the dist tag fetch the corresponding + * version and make sure this version is used throughout the repository. + * + * If you work on this file: + * WARNING: This script can only use built-in modules since it has to run before + * `pnpm install` + */ +import childProcess from 'child_process'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { promisify } from 'util'; +import { getWorkspaceRoot } from './utils.mjs'; + +// TODO: reuse the `useReactVersion.mjs` from the monorepo + +const exec = promisify(childProcess.exec); + +// packages published from the react monorepo using the same version +const reactPackageNames = ['react', 'react-dom', 'react-is', 'react-test-renderer', 'scheduler']; +const devDependenciesPackageNames = ['@mnajdova/enzyme-adapter-react-18', '@testing-library/react']; + +// if we need to support more versions we will need to add new mapping here +const additionalVersionsMappings = { + 17: { + '@mnajdova/enzyme-adapter-react-18': 'npm:@eps1lon/enzyme-adapter-react-17', + '@testing-library/react': '^12.1.0', + }, + 19: {}, +}; + +async function main(version) { + if (typeof version !== 'string') { + throw new TypeError(`expected version: string but got '${version}'`); + } + + if (version === 'stable') { + console.log('Nothing to do with stable'); + return; + } + + const packageJsonPath = path.resolve(getWorkspaceRoot(), 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, { encoding: 'utf8' })); + + // the version is something in format: "17.0.0" + let majorVersion = null; + + if (version.startsWith('^') || version.startsWith('~') || !Number.isNaN(version.charAt(0))) { + majorVersion = version.replace('^', '').replace('~', '').split('.')[0]; + } + + await Promise.all( + reactPackageNames.map(async (reactPackageName) => { + const { stdout: versions } = await exec(`npm dist-tag ls ${reactPackageName} ${version}`); + const tagMapping = versions.split('\n').find((mapping) => { + return mapping.startsWith(`${version}: `); + }); + + let packageVersion = null; + + if (tagMapping === undefined) { + // Some specific version is being requested + if (majorVersion) { + packageVersion = version; + if (reactPackageName === 'scheduler') { + // get the scheduler version from the react-dom's dependencies entry + const { stdout: reactDOMDependenciesString } = await exec( + `npm view --json react-dom@${version} dependencies`, + ); + packageVersion = JSON.parse(reactDOMDependenciesString).scheduler; + } + } else { + throw new Error(`Could not find '${version}' in "${versions}"`); + } + } else { + packageVersion = tagMapping.replace(`${version}: `, ''); + } + + packageJson.resolutions[reactPackageName] = packageVersion; + }), + ); + + // At this moment all dist tags reference React 18 version, so we don't need + // to update these dependencies unless an older version is used, or when the + // next/experimental dist tag reference to a future version of React + // packageJson.devDependencies['@mnajdova/enzyme-adapter-react-18'] = + // 'npm:@mnajdova/enzyme-adapter-react-next'; + // packageJson.devDependencies['@testing-library/react'] = 'alpha'; + + if (majorVersion && additionalVersionsMappings[majorVersion]) { + devDependenciesPackageNames.forEach((packageName) => { + if (!additionalVersionsMappings[majorVersion][packageName]) { + throw new Error( + `Version ${majorVersion} does not have version defined for the ${packageName}`, + ); + } + packageJson.devDependencies[packageName] = + additionalVersionsMappings[majorVersion][packageName]; + }); + } + + // add newline for clean diff + fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}${os.EOL}`); +} + +const [version = process.env.REACT_VERSION] = process.argv.slice(2); +main(version).catch((error) => { + console.error(error); + process.exit(1); +});