From ebc2831d5b29c94bd9aba9989e70db11c6cfd705 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Wed, 31 Jan 2024 19:32:08 -0800 Subject: [PATCH] set unified version (#42776) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42776 Changelog: [Internal] set all monorepo packages (including react-native) to one version and update all inter-dependencies (including the template) Reviewed By: huntie Differential Revision: D53251917 fbshipit-source-id: 95330ca66dcb7234a3f09752ecc3ed9087ced4bf --- jest.config.js | 1 + scripts/monorepo/for-each-package.js | 50 +++---- scripts/monorepo/get-and-update-packages.js | 2 +- .../packages/monorepo-pkg-a/package.json | 12 ++ .../packages/monorepo-pkg-b/package.json | 10 ++ .../packages/monorepo-pkg-c/package.json | 9 ++ .../packages/react-native/package.json | 12 ++ .../react-native/template/package.json | 15 +++ .../__snapshots__/set-version-test.js.snap | 79 ++++++++++++ .../set-version/__tests__/set-version-test.js | 46 +++++++ scripts/releases/set-version/index.js | 122 ++++++++++++++++++ 11 files changed, 335 insertions(+), 23 deletions(-) create mode 100644 scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-a/package.json create mode 100644 scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-b/package.json create mode 100644 scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-c/package.json create mode 100644 scripts/releases/set-version/__tests__/__fixtures__/packages/react-native/package.json create mode 100644 scripts/releases/set-version/__tests__/__fixtures__/packages/react-native/template/package.json create mode 100644 scripts/releases/set-version/__tests__/__snapshots__/set-version-test.js.snap create mode 100644 scripts/releases/set-version/__tests__/set-version-test.js create mode 100644 scripts/releases/set-version/index.js diff --git a/jest.config.js b/jest.config.js index c21230d2913e13..eb43b0845addd3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -48,6 +48,7 @@ module.exports = { '/packages/react-native/jest/ReactNativeInternalFeatureFlagsMock.js', }, moduleFileExtensions: ['fb.js'].concat(defaults.moduleFileExtensions), + modulePathIgnorePatterns: ['scripts/.*/__fixtures__/'], unmockedModulePathPatterns: [ 'node_modules/react/', 'packages/react-native/Libraries/Renderer', diff --git a/scripts/monorepo/for-each-package.js b/scripts/monorepo/for-each-package.js index 49c4ec9107e2b9..aed8abe6112b22 100644 --- a/scripts/monorepo/for-each-package.js +++ b/scripts/monorepo/for-each-package.js @@ -4,48 +4,54 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow strict-local * @format */ const {readdirSync, readFileSync} = require('fs'); const path = require('path'); -const ROOT_LOCATION = path.join(__dirname, '..', '..'); -const PACKAGES_LOCATION = path.join(ROOT_LOCATION, 'packages'); +const REPO_ROOT = path.join(path.dirname(__filename), '..', '..'); +const PACKAGES_LOCATION = path.join(REPO_ROOT, 'packages'); -const DEFAULT_OPTIONS = {includeReactNative: false}; +const DEFAULT_OPTIONS /*: Options */ = {includeReactNative: false}; + +/*:: +type PackageJSON = { + name: string, + private?: ?boolean, + version: string, + dependencies: {[string]: string}, + devDependencies: {[string]: string}, + ... +}; + +type Options = { + includeReactNative?: ?boolean, +}; +*/ /** * Function, which returns an array of all directories inside specified location - * - * @param {string} source Path to directory, where this should be executed - * @returns {string[]} List of directories names */ -const getDirectories = source => +const getDirectories = (source /*: string */) /*: Array */ => readdirSync(source, {withFileTypes: true}) .filter(file => file.isDirectory()) - .map(directory => directory.name); - -/** - * @callback forEachPackageCallback - * @param {string} packageAbsolutePath - * @param {string} packageRelativePathFromRoot - * @param {Object} packageManifest - */ - + .map(directory => directory.name.toString()); /** * Iterate through every package inside /packages (ignoring react-native) and call provided callback for each of them - * - * @param {forEachPackageCallback} callback The callback which will be called for each package - * @param {{includeReactNative: (boolean|undefined)}} [options={}] description */ -const forEachPackage = (callback, options = DEFAULT_OPTIONS) => { +const forEachPackage = ( + callback /*: (string, string, PackageJSON) => void */, + options /*: Options */ = DEFAULT_OPTIONS, +) => { const {includeReactNative} = options; // We filter react-native package on purpose, so that no CI's script will be executed for this package in future // Unless includeReactNative options is provided const packagesDirectories = getDirectories(PACKAGES_LOCATION).filter( - directoryName => directoryName !== 'react-native' || includeReactNative, + directoryName => + directoryName !== 'react-native' || includeReactNative === true, ); packagesDirectories.forEach(packageDirectory => { @@ -53,7 +59,7 @@ const forEachPackage = (callback, options = DEFAULT_OPTIONS) => { const packageRelativePathFromRoot = path.join('packages', packageDirectory); const packageManifest = JSON.parse( - readFileSync(path.join(packageAbsolutePath, 'package.json')), + readFileSync(path.join(packageAbsolutePath, 'package.json')).toString(), ); callback(packageAbsolutePath, packageRelativePathFromRoot, packageManifest); diff --git a/scripts/monorepo/get-and-update-packages.js b/scripts/monorepo/get-and-update-packages.js index 67b74e5383ef93..0faa6e927005f2 100644 --- a/scripts/monorepo/get-and-update-packages.js +++ b/scripts/monorepo/get-and-update-packages.js @@ -54,7 +54,7 @@ type PackageMetadata = {| * is an object that contains the absolute path to the package and the packageJson. */ function getPackagesToPublish() /*: PackageMap */ { - let packages = {}; + let packages /*: PackageMap */ = {}; forEachPackage( (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { diff --git a/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-a/package.json b/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-a/package.json new file mode 100644 index 00000000000000..af677cc30b5dea --- /dev/null +++ b/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-a/package.json @@ -0,0 +1,12 @@ +{ + "name": "@monorepo/pkg-a", + "version": "0.0.1", + "description": "@monorepo/pkg-a", + "dependencies": { + "@monorepo/pkg-b": "0.0.1", + "@monorepo/other": "0.0.1" + }, + "devDependencies": { + "@monorepo/pkg-c": "0.0.1" + } +} diff --git a/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-b/package.json b/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-b/package.json new file mode 100644 index 00000000000000..4cbb3cd94e3296 --- /dev/null +++ b/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-b/package.json @@ -0,0 +1,10 @@ +{ + "name": "@monorepo/pkg-b", + "version": "0.0.2", + "description": "@monorepo/pkg-b", + "dependencies": { + "@monorepo/pkg-c": "0.0.1", + "metro-config": "^0.80.3", + "metro-runtime": "^0.80.3" + } +} diff --git a/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-c/package.json b/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-c/package.json new file mode 100644 index 00000000000000..ce6ccdc7e1bb29 --- /dev/null +++ b/scripts/releases/set-version/__tests__/__fixtures__/packages/monorepo-pkg-c/package.json @@ -0,0 +1,9 @@ +{ + "name": "@monorepo/pkg-c", + "version": "0.0.3", + "description": "@monorepo/pkg-c", + "dependencies": { + "metro-config": "^0.80.3", + "metro-runtime": "^0.80.3" + } +} diff --git a/scripts/releases/set-version/__tests__/__fixtures__/packages/react-native/package.json b/scripts/releases/set-version/__tests__/__fixtures__/packages/react-native/package.json new file mode 100644 index 00000000000000..864d58824c6d5b --- /dev/null +++ b/scripts/releases/set-version/__tests__/__fixtures__/packages/react-native/package.json @@ -0,0 +1,12 @@ +{ + "name": "react-native", + "version": "0.0.0", + "description": "fake react native package", + "dependencies": { + "@monorepo/pkg-a": "0.0.1", + "@monorepo/pkg-b": "0.0.1", + "@monorepo/pkg-c": "0.0.1", + "metro-config": "^0.80.3", + "metro-runtime": "^0.80.3" + } +} diff --git a/scripts/releases/set-version/__tests__/__fixtures__/packages/react-native/template/package.json b/scripts/releases/set-version/__tests__/__fixtures__/packages/react-native/template/package.json new file mode 100644 index 00000000000000..69c4a454ee627d --- /dev/null +++ b/scripts/releases/set-version/__tests__/__fixtures__/packages/react-native/template/package.json @@ -0,0 +1,15 @@ +{ + "name": "react-native-test-template", + "version": "0.0.1", + "private": true, + "dependencies": { + "react": "18.2.0", + "react-native": "0.0.0" + }, + "devDependencies": { + "@monorepo/pkg-a": "0.0.1", + "@monorepo/pkg-c": "0.0.0", + "@types/react": "^18.2.6", + "@types/react-test-renderer": "^18.0.0" + } +} diff --git a/scripts/releases/set-version/__tests__/__snapshots__/set-version-test.js.snap b/scripts/releases/set-version/__tests__/__snapshots__/set-version-test.js.snap new file mode 100644 index 00000000000000..bbf805d8fc4bde --- /dev/null +++ b/scripts/releases/set-version/__tests__/__snapshots__/set-version-test.js.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`setVersion updates all public packages to version: monorepo-pkg-a 1`] = ` +"{ + \\"name\\": \\"@monorepo/pkg-a\\", + \\"version\\": \\"0.80.0\\", + \\"description\\": \\"@monorepo/pkg-a\\", + \\"dependencies\\": { + \\"@monorepo/pkg-b\\": \\"0.80.0\\", + \\"@monorepo/other\\": \\"0.0.1\\" + }, + \\"devDependencies\\": { + \\"@monorepo/pkg-c\\": \\"0.80.0\\" + } +} +" +`; + +exports[`setVersion updates all public packages to version: monorepo-pkg-b 1`] = ` +"{ + \\"name\\": \\"@monorepo/pkg-b\\", + \\"version\\": \\"0.80.0\\", + \\"description\\": \\"@monorepo/pkg-b\\", + \\"dependencies\\": { + \\"@monorepo/pkg-c\\": \\"0.80.0\\", + \\"metro-config\\": \\"^0.80.3\\", + \\"metro-runtime\\": \\"^0.80.3\\" + } +} +" +`; + +exports[`setVersion updates all public packages to version: monorepo-pkg-c 1`] = ` +"{ + \\"name\\": \\"@monorepo/pkg-c\\", + \\"version\\": \\"0.80.0\\", + \\"description\\": \\"@monorepo/pkg-c\\", + \\"dependencies\\": { + \\"metro-config\\": \\"^0.80.3\\", + \\"metro-runtime\\": \\"^0.80.3\\" + } +} +" +`; + +exports[`setVersion updates all public packages to version: react-native 1`] = ` +"{ + \\"name\\": \\"react-native\\", + \\"version\\": \\"0.80.0\\", + \\"description\\": \\"fake react native package\\", + \\"dependencies\\": { + \\"@monorepo/pkg-a\\": \\"0.80.0\\", + \\"@monorepo/pkg-b\\": \\"0.80.0\\", + \\"@monorepo/pkg-c\\": \\"0.80.0\\", + \\"metro-config\\": \\"^0.80.3\\", + \\"metro-runtime\\": \\"^0.80.3\\" + } +} +" +`; + +exports[`setVersion updates all public packages to version: template 1`] = ` +"{ + \\"name\\": \\"react-native-test-template\\", + \\"version\\": \\"0.0.1\\", + \\"private\\": true, + \\"dependencies\\": { + \\"react\\": \\"18.2.0\\", + \\"react-native\\": \\"0.80.0\\" + }, + \\"devDependencies\\": { + \\"@monorepo/pkg-a\\": \\"0.80.0\\", + \\"@monorepo/pkg-c\\": \\"0.80.0\\", + \\"@types/react\\": \\"^18.2.6\\", + \\"@types/react-test-renderer\\": \\"^18.0.0\\" + } +} +" +`; diff --git a/scripts/releases/set-version/__tests__/set-version-test.js b/scripts/releases/set-version/__tests__/set-version-test.js new file mode 100644 index 00000000000000..c30ffcd1c9dfeb --- /dev/null +++ b/scripts/releases/set-version/__tests__/set-version-test.js @@ -0,0 +1,46 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +const setVersion = require('../index'); +const path = require('path'); + +describe('setVersion', () => { + beforeAll(() => { + jest.mock('path', () => { + // $FlowIgnore[underconstrained-implicit-instantiation] + const originalPath = jest.requireActual('path'); + return { + ...originalPath, + dirname: () => originalPath.join(__dirname, '__fixtures__/two/levels'), + }; + }); + + jest.mock('fs', () => { + // $FlowIgnore[underconstrained-implicit-instantiation] + const originalFs = jest.requireActual('fs'); + + return { + ...originalFs, + writeFileSync: (packagePath, content) => { + expect(content).toMatchSnapshot( + path.basename(path.join(packagePath, '..')), + ); + }, + }; + }); + }); + test('updates all public packages to version', () => { + setVersion('0.80.0'); + }); + afterAll(() => { + jest.unmock('path'); + jest.unmock('fs'); + }); +}); diff --git a/scripts/releases/set-version/index.js b/scripts/releases/set-version/index.js new file mode 100644 index 00000000000000..a0cdeee814effb --- /dev/null +++ b/scripts/releases/set-version/index.js @@ -0,0 +1,122 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react-native + */ + +'use strict'; + +const forEachPackage = require('../../monorepo/for-each-package'); +const {readFileSync, writeFileSync} = require('fs'); +const path = require('path'); +const yargs = require('yargs'); + +function getPublicPackages() { + // eslint-disable-next-line func-call-spacing + const packages = new Set /*::*/(); + forEachPackage( + (_, __, packageJson) => { + if (packageJson.private !== true) { + packages.add(packageJson.name); + } + }, + {includeReactNative: true}, + ); + return packages; +} + +function setVersion(version /*: string */) { + const publicPackages = getPublicPackages(); + + forEachPackage( + (packageAbsolutePath, _, packageJson) => { + if (packageJson.private === true) { + return; + } + + packageJson.version = version; + + if (packageJson.dependencies != null) { + for (const dependency of Object.keys(packageJson.dependencies)) { + if (publicPackages.has(dependency)) { + packageJson.dependencies[dependency] = version; + } + } + } + + if (packageJson.devDependencies != null) { + for (const devDependency of Object.keys(packageJson.devDependencies)) { + if (publicPackages.has(devDependency)) { + packageJson.devDependencies[devDependency] = version; + } + } + } + + writeFileSync( + path.join(packageAbsolutePath, 'package.json'), + JSON.stringify(packageJson, null, 2) + '\n', + 'utf-8', + ); + + // Update template package.json + if (packageJson.name === 'react-native') { + const templatePackageJsonPath = path.join( + packageAbsolutePath, + 'template', + 'package.json', + ); + const templatePackageJson = JSON.parse( + readFileSync(templatePackageJsonPath).toString(), + ); + if (templatePackageJson.dependencies != null) { + for (const dependency of Object.keys( + templatePackageJson.dependencies, + )) { + if (publicPackages.has(dependency)) { + templatePackageJson.dependencies[dependency] = version; + } + } + } + + if (templatePackageJson.devDependencies != null) { + for (const devDependency of Object.keys( + templatePackageJson.devDependencies, + )) { + if (publicPackages.has(devDependency)) { + templatePackageJson.devDependencies[devDependency] = version; + } + } + } + writeFileSync( + templatePackageJsonPath, + JSON.stringify(templatePackageJson, null, 2) + '\n', + 'utf-8', + ); + } + }, + {includeReactNative: true}, + ); +} + +module.exports = setVersion; + +if (require.main === module) { + const {toVersion} = yargs(process.argv.slice(2)) + .command( + '$0 ', + 'Update all monorepo packages to ', + args => + args.positional('to-version', { + type: 'string', + description: 'Set the version of all packages to this value', + required: true, + }), + ) + .parseSync(); + setVersion(toVersion); +}