From 974f7f57c5145326bdaedd4e45f7ee467727079c Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Fri, 9 Feb 2024 15:34:24 -0800 Subject: [PATCH] Use set-version for nightly publishes Summary: Changelog: [Internal] - Update publish-npm to use `set-version` for nightly builds Now that `set-version` basically does what `set-rn-version` does, this diff uses this logic for nightlies only (as dry-run/pre-alpha variants are non-functional right now) This does not change the flow of build-type `'release'` -- that will still use `set-rn-version` via CircleCI ([job](https://fburl.com/code/6xo3ijwg), [script](https://fburl.com/code/bo8np0tb)) We will eventually replace that too but that will be later. This allows us to delete `get-and-update-packages.js` which was a helper written specifically for updating monorepo packages for nightlies. The purpose of this is to eventually conform all version updates to use `set-version` in all types of releases (nightlies, stable) Reviewed By: cipolleschi Differential Revision: D53487874 --- .../__tests__/get-and-update-packages-test.js | 199 ------------------ scripts/monorepo/get-and-update-packages.js | 170 --------------- .../releases-ci/__tests__/publish-npm-test.js | 33 ++- scripts/releases-ci/publish-npm.js | 16 +- 4 files changed, 17 insertions(+), 401 deletions(-) delete mode 100644 scripts/monorepo/__tests__/get-and-update-packages-test.js delete mode 100644 scripts/monorepo/get-and-update-packages.js diff --git a/scripts/monorepo/__tests__/get-and-update-packages-test.js b/scripts/monorepo/__tests__/get-and-update-packages-test.js deleted file mode 100644 index 44a51f5ad3cc26..00000000000000 --- a/scripts/monorepo/__tests__/get-and-update-packages-test.js +++ /dev/null @@ -1,199 +0,0 @@ -/** - * 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. - * - * @format - */ - -const getAndUpdatePackages = require('../get-and-update-packages'); -const { - expectedPackages, - mockPackages, -} = require('./__fixtures__/get-and-update-packages-fixtures'); -const path = require('path'); - -const writeFileSyncMock = jest.fn(); -const publishPackageMock = jest.fn(); -const getPackageVersionStrByTag = jest.fn(); - -function forEachPackageThatShouldBePublished(callback) { - mockPackages.forEach(package => { - if ( - package.packageManifest.name === 'react-native' || - package.packageManifest.private || - package.packageManifest.name === '@react-native/not_published' - ) { - return; - } - callback(package); - }); -} - -jest - .mock('fs', () => ({ - writeFileSync: writeFileSyncMock, - })) - .mock('../for-each-package', () => callback => { - mockPackages.forEach( - ({packageManifest, packageAbsolutePath, packageRelativePathFromRoot}) => - callback( - packageAbsolutePath, - packageRelativePathFromRoot, - packageManifest, - ), - ); - }) - .mock('../../npm-utils', () => ({ - publishPackage: publishPackageMock, - getPackageVersionStrByTag: getPackageVersionStrByTag, - })); - -describe('getAndUpdatePackages', () => { - beforeEach(() => { - jest.clearAllMocks(); - - getPackageVersionStrByTag.mockImplementation(packageName => { - if (packageName === '@react-native/not_published') { - throw new Error(`Can't find package with name ${packageName}`); - } - return ''; - }); - - // Silence logs. - jest.spyOn(console, 'log').mockImplementation(() => {}); - }); - - it('Publishes the nightly version', () => { - const nightlyVersion = '0.73.0-nightly-202108-shortcommit'; - publishPackageMock.mockImplementation(() => ({code: 0})); - - const updatedPackages = getAndUpdatePackages(nightlyVersion, 'nightly'); - - expect(writeFileSyncMock).toHaveBeenCalledTimes(6); - forEachPackageThatShouldBePublished(package => { - expect(writeFileSyncMock).toHaveBeenCalledWith( - path.join(package.packageAbsolutePath, 'package.json'), - JSON.stringify( - expectedPackages[package.packageManifest.name](nightlyVersion), - null, - 2, - ) + '\n', - 'utf-8', - ); - }); - - expect(publishPackageMock).toHaveBeenCalledTimes(6); - forEachPackageThatShouldBePublished(package => { - expect(publishPackageMock).toHaveBeenCalledWith( - package.packageAbsolutePath, - {otp: undefined, tags: ['nightly']}, - ); - }); - - let expectedResult = {}; - forEachPackageThatShouldBePublished(package => { - expectedResult[package.packageManifest.name] = - package.packageManifest.version; - }); - expect(updatedPackages).toEqual(expectedResult); - }); - - it('Throws when a package fails to publish', () => { - const nightlyVersion = '0.73.0-nightly-202108-shortcommit'; - let publishCalls = 0; - publishPackageMock.mockImplementation(() => { - publishCalls += 1; - if (publishCalls === 3) { - return {code: -1}; - } - return {code: 0}; - }); - - expect(() => { - getAndUpdatePackages(nightlyVersion, 'nightly'); - }).toThrow(); - - expect(writeFileSyncMock).toHaveBeenCalledTimes(6); - forEachPackageThatShouldBePublished(package => { - expect(writeFileSyncMock).toHaveBeenCalledWith( - path.join(package.packageAbsolutePath, 'package.json'), - JSON.stringify( - expectedPackages[package.packageManifest.name](nightlyVersion), - null, - 2, - ) + '\n', - 'utf-8', - ); - }); - - expect(publishPackageMock).toHaveBeenCalledTimes(3); - }); - - it('Publishes the prealpha versions', () => { - const version = '0.0.0-prealpha-2023100416'; - publishPackageMock.mockImplementation(() => ({code: 0})); - - const updatedPackages = getAndUpdatePackages(version, 'prealpha'); - - expect(writeFileSyncMock).toHaveBeenCalledTimes(6); - forEachPackageThatShouldBePublished(package => { - expect(writeFileSyncMock).toHaveBeenCalledWith( - path.join(package.packageAbsolutePath, 'package.json'), - JSON.stringify( - expectedPackages[package.packageManifest.name](version), - null, - 2, - ) + '\n', - 'utf-8', - ); - }); - - expect(publishPackageMock).toHaveBeenCalledTimes(6); - forEachPackageThatShouldBePublished(package => { - expect(publishPackageMock).toHaveBeenCalledWith( - package.packageAbsolutePath, - {otp: undefined, tags: ['prealpha']}, - ); - }); - - let expectedResult = {}; - forEachPackageThatShouldBePublished(package => { - expectedResult[package.packageManifest.name] = - package.packageManifest.version; - }); - expect(updatedPackages).toEqual(expectedResult); - }); - - it('Throws when a package fails to publish with prealpha', () => { - const version = '0.0.0-prealpha-2023100416'; - let publishCalls = 0; - publishPackageMock.mockImplementation(() => { - publishCalls += 1; - if (publishCalls === 3) { - return {code: -1}; - } - return {code: 0}; - }); - - expect(() => { - getAndUpdatePackages(version, 'prealpha'); - }).toThrow(); - - expect(writeFileSyncMock).toHaveBeenCalledTimes(6); - forEachPackageThatShouldBePublished(package => { - expect(writeFileSyncMock).toHaveBeenCalledWith( - path.join(package.packageAbsolutePath, 'package.json'), - JSON.stringify( - expectedPackages[package.packageManifest.name](version), - null, - 2, - ) + '\n', - 'utf-8', - ); - }); - - expect(publishPackageMock).toHaveBeenCalledTimes(3); - }); -}); diff --git a/scripts/monorepo/get-and-update-packages.js b/scripts/monorepo/get-and-update-packages.js deleted file mode 100644 index 0faa6e927005f2..00000000000000 --- a/scripts/monorepo/get-and-update-packages.js +++ /dev/null @@ -1,170 +0,0 @@ -/** - * 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 - * @oncall react_native - */ - -const {publishPackage} = require('../npm-utils'); -const {getPackageVersionStrByTag} = require('../npm-utils'); -const forEachPackage = require('./for-each-package'); -const {writeFileSync} = require('fs'); -const path = require('path'); - -/** - * Get the latest version of each monorepo package and publishes a new package if there have been updates. - * Returns a map of monorepo packages and their latest version. - * - * This is called by `react-native`'s nightly and prealpha job. - * Note: This does not publish `package/react-native`'s nightly/prealpha. That is handled in `publish-npm`. - */ -function getAndUpdatePackages( - version /*: string */, - tag /*: 'nightly' | 'prealpha' */, -) /*: {| [string]: string|}*/ { - const packages = getPackagesToPublish(); - - updateDependencies(packages, version); - - publishPackages(packages, tag); - - return reducePackagesToNameVersion(packages); -} - -/*:: -type PackageMap = {|[string]: PackageMetadata |} -type PackageMetadata = {| - // The absolute path to the package - path: string, - - // The parsed package.json contents - packageJson: Object, -|}; -*/ - -/** - * Extract package metadata from the monorepo - * It skips react-native, the packages marked as private and packages not already published on NPM. - * - * @return Dictionary where the string is the name of the package and the PackageMetadata - * is an object that contains the absolute path to the package and the packageJson. - */ -function getPackagesToPublish() /*: PackageMap */ { - let packages /*: PackageMap */ = {}; - - forEachPackage( - (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => { - if (packageManifest.private || !isPublishedOnNPM(packageManifest.name)) { - console.log( - `\u23F1 Skipping publishing for ${packageManifest.name}. It is either pivate or unpublished`, - ); - return; - } - packages[packageManifest.name] = { - path: packageAbsolutePath, - packageJson: packageManifest, - }; - }, - ); - - return packages; -} - -function isPublishedOnNPM(packageName /*: string */) /*: boolean */ { - try { - getPackageVersionStrByTag(packageName); - return true; - } catch (e) { - return false; - } -} - -/** - * Update the dependencies in the packages to the passed version. - * The function update the packages dependencies and devDepenencies if they contains other packages in the monorepo. - * The function also writes the updated package.json to disk. - */ -function updateDependencies(packages /*: PackageMap */, version /*: string */) { - Object.keys(packages).forEach(packageName => { - const package = packages[packageName]; - const packageManifest = package.packageJson; - - if (packageManifest.dependencies) { - for (const dependency of Object.keys(packageManifest.dependencies)) { - if (packages[dependency]) { - // the dependency is in the monorepo - packages[packageName].packageJson.dependencies[dependency] = version; - } - } - } - - if (packageManifest.devDependencies) { - for (const dependency of Object.keys(packageManifest.devDependencies)) { - if (packages[dependency]) { - // the dependency is in the monorepo - packages[packageName].packageJson.devDependencies[dependency] = - version; - } - } - } - - packages[packageName].packageJson.version = version; - - writeFileSync( - path.join(packages[packageName].path, 'package.json'), - JSON.stringify(packages[packageName].packageJson, null, 2) + '\n', - 'utf-8', - ); - }); -} - -/** - * Publish the passed set of packages to npm with the passed tag. - * In case a package fails to be published, it throws an error, stopping the nightly publishing completely - */ -function publishPackages( - packages /*: PackageMap */, - tag /*: 'nightly' | 'prealpha' */, -) { - for (const [packageName, packageMetadata] of Object.entries(packages)) { - const packageAbsolutePath = packageMetadata.path; - const version = packageMetadata.packageJson.version; - - const result = publishPackage(packageAbsolutePath, { - tags: [tag], - otp: process.env.NPM_CONFIG_OTP, - }); - - if (result.code !== 0) { - throw new Error( - `\u274c Failed to publish version ${version} of ${packageName}. npm publish exited with code ${result.code}:`, - ); - } - - console.log(`\u2705 Successfully published new version of ${packageName}`); - } -} - -/** - * Reduces the Dictionary to a Dictionary. - * where the key is the name of the package and the value is the version number. - * - * @return Dictionary with package name and the new version number. - */ -function reducePackagesToNameVersion( - packages /*: PackageMap */, -) /*: {| [string]: string |} */ { - return Object.keys(packages).reduce( - (result /*: {| [string]: string |} */, name /*: string */) => { - result[name] = packages[name].packageJson.version; - return result; - }, - {}, - ); -} - -module.exports = getAndUpdatePackages; diff --git a/scripts/releases-ci/__tests__/publish-npm-test.js b/scripts/releases-ci/__tests__/publish-npm-test.js index aceab5f9635d02..5c830c1e535236 100644 --- a/scripts/releases-ci/__tests__/publish-npm-test.js +++ b/scripts/releases-ci/__tests__/publish-npm-test.js @@ -13,7 +13,7 @@ const echoMock = jest.fn(); const exitMock = jest.fn(); const consoleErrorMock = jest.fn(); const isTaggedLatestMock = jest.fn(); -const setReactNativeVersionMock = jest.fn(); +const setVersionMock = jest.fn(); const publishAndroidArtifactsToMavenMock = jest.fn(); const removeNewArchFlags = jest.fn(); const env = process.env; @@ -29,19 +29,11 @@ jest getCurrentCommit: () => 'currentco_mmit', isTaggedLatest: isTaggedLatestMock, })) - .mock('path', () => ({ - ...jest.requireActual('path'), - join: () => '../../packages/react-native', - })) - .mock('fs') .mock('../../releases/utils/release-utils', () => ({ generateAndroidArtifacts: jest.fn(), publishAndroidArtifactsToMaven: publishAndroidArtifactsToMavenMock, })) - .mock('../../releases/set-rn-version', () => ({ - setReactNativeVersion: setReactNativeVersionMock, - })) - .mock('../../monorepo/get-and-update-packages') + .mock('../../releases/set-version', () => setVersionMock) .mock('../../releases/remove-new-arch-flags', () => ({ removeNewArchFlags, })); @@ -49,6 +41,10 @@ jest const date = new Date('2023-04-20T23:52:39.543Z'); const {publishNpm} = require('../publish-npm'); +const path = require('path'); + +const REPO_ROOT = path.resolve(__filename, '../../../..'); + let consoleError; describe('publish-npm', () => { @@ -88,11 +84,7 @@ describe('publish-npm', () => { expect(echoMock).toHaveBeenCalledWith( 'Skipping `npm publish` because --dry-run is set.', ); - expect(setReactNativeVersionMock).toBeCalledWith( - '1000.0.0-currentco', - null, - 'dry-run', - ); + expect(setVersionMock).toBeCalledWith('1000.0.0-currentco'); }); }); @@ -106,6 +98,7 @@ describe('publish-npm', () => { await publishNpm('nightly'); expect(removeNewArchFlags).not.toHaveBeenCalled(); + expect(setVersionMock).toBeCalledWith(expectedVersion); expect(publishAndroidArtifactsToMavenMock).toHaveBeenCalledWith( expectedVersion, 'nightly', @@ -123,7 +116,7 @@ describe('publish-npm', () => { it('should fail to set version', async () => { execMock.mockReturnValueOnce({stdout: '0.81.0-rc.1\n', code: 0}); const expectedVersion = '0.82.0-nightly-20230420-currentco'; - setReactNativeVersionMock.mockImplementation(() => { + setVersionMock.mockImplementation(() => { throw new Error('something went wrong'); }); @@ -166,7 +159,7 @@ describe('publish-npm', () => { ); expect(execMock).toHaveBeenCalledWith( `npm publish --tag 0.81-stable --otp otp`, - {cwd: '../../packages/react-native'}, + {cwd: path.join(REPO_ROOT, 'packages/react-native')}, ); expect(echoMock).toHaveBeenCalledWith( `Published to npm ${expectedVersion}`, @@ -191,7 +184,7 @@ describe('publish-npm', () => { ); expect(execMock).toHaveBeenCalledWith( `npm publish --tag latest --otp ${process.env.NPM_CONFIG_OTP}`, - {cwd: '../../packages/react-native'}, + {cwd: path.join(REPO_ROOT, 'packages/react-native')}, ); expect(echoMock).toHaveBeenCalledWith( `Published to npm ${expectedVersion}`, @@ -216,7 +209,7 @@ describe('publish-npm', () => { ); expect(execMock).toHaveBeenCalledWith( `npm publish --tag latest --otp ${process.env.NPM_CONFIG_OTP}`, - {cwd: '../../packages/react-native'}, + {cwd: path.join(REPO_ROOT, 'packages/react-native')}, ); expect(echoMock).toHaveBeenCalledWith(`Failed to publish package to npm`); expect(exitMock).toHaveBeenCalledWith(1); @@ -239,7 +232,7 @@ describe('publish-npm', () => { ); expect(execMock).toHaveBeenCalledWith( `npm publish --tag next --otp ${process.env.NPM_CONFIG_OTP}`, - {cwd: '../../packages/react-native'}, + {cwd: path.join(REPO_ROOT, 'packages/react-native')}, ); expect(echoMock).toHaveBeenCalledWith( `Published to npm ${expectedVersion}`, diff --git a/scripts/releases-ci/publish-npm.js b/scripts/releases-ci/publish-npm.js index 5e5b6f6a798deb..57387a04574832 100755 --- a/scripts/releases-ci/publish-npm.js +++ b/scripts/releases-ci/publish-npm.js @@ -15,10 +15,9 @@ import type {BuildType} from '../releases/utils/version-utils'; */ -const getAndUpdatePackages = require('../monorepo/get-and-update-packages'); const {getNpmInfo, publishPackage} = require('../npm-utils'); const {removeNewArchFlags} = require('../releases/remove-new-arch-flags'); -const {setReactNativeVersion} = require('../releases/set-rn-version'); +const setVersion = require('../releases/set-version'); const { generateAndroidArtifacts, publishAndroidArtifactsToMaven, @@ -74,18 +73,11 @@ async function publishNpm(buildType /*: BuildType */) /*: Promise */ { removeNewArchFlags(); } - // Here we update the react-native package and template package with the right versions - // For releases, CircleCI job `prepare_package_for_release` handles this + // Set same version for all monorepo packages + // For stable releases, CircleCI job `prepare_package_for_release` handles this if (['dry-run', 'nightly', 'prealpha'].includes(buildType)) { - // Publish monorepo nightlies and prealphas if there are updates, returns the new version for each package - const monorepoVersions = - // $FlowFixMe[incompatible-call] - buildType === 'dry-run' ? null : getAndUpdatePackages(version, buildType); - try { - // Update the react-native and template packages with the react-native version - // and nightly versions of monorepo deps - await setReactNativeVersion(version, monorepoVersions, buildType); + await setVersion(version); } catch (e) { console.error(`Failed to set version number to ${version}`); console.error(e);