Publish nightly monorepo packages (#37556)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/37556

Changelog: [Internal]

Before making this change, we need to publish `nightly` versions of all existing monorepo dependencies.

Reviewed By: hoxyq

Differential Revision: D46117197

fbshipit-source-id: bcf6364e068579e63ca19e8161dcd32de4353e56
This commit is contained in:
Luna Wei
2023-06-02 17:40:52 -07:00
committed by Facebook GitHub Bot
parent e25c6632a2
commit fd9e295bef
6 changed files with 289 additions and 3 deletions
+2 -1
View File
@@ -32,7 +32,8 @@ jest
.mock('./../release-utils', () => ({
generateAndroidArtifacts: jest.fn(),
publishAndroidArtifactsToMaven: publishAndroidArtifactsToMavenMock,
}));
}))
.mock('../monorepo/publish-nightly-for-each-changed-package');
const date = new Date('2023-04-20T23:52:39.543Z');
@@ -0,0 +1,101 @@
/**
* 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 publishNightlyForEachChangedPackage = require('../publish-nightly-for-each-changed-package');
const mockPackages = [
{
packageManifest: {name: 'packageA', version: 'local-version'},
packageAbsolutePath: '/some/place/packageA',
packageRelativePathFromRoot: './place/packageA',
},
];
const execMock = jest.fn();
const writeFileSyncMock = jest.fn();
const diffPackagesMock = jest.fn();
const publishPackageMock = jest.fn();
jest
.mock('shelljs', () => ({
exec: execMock,
rm: jest.fn(),
}))
.mock('fs', () => ({
writeFileSync: writeFileSyncMock,
}))
.mock('../for-each-package', () => callback => {
mockPackages.forEach(
({packageManifest, packageAbsolutePath, packageRelativePathFromRoot}) =>
callback(
packageAbsolutePath,
packageRelativePathFromRoot,
packageManifest,
),
);
})
.mock('../../scm-utils', () => ({
restore: jest.fn(),
}))
.mock('../../npm-utils', () => ({
getPackageVersionStrByTag: () => 'published-nightly-version',
diffPackages: diffPackagesMock,
publishPackage: publishPackageMock,
pack: jest.fn(),
}));
describe('publishNightlyForEachChangedPackage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('publishes because there are changes', () => {
const nightlyVersion = '0.73.0-nightly-202108-shortcommit';
publishPackageMock.mockImplementationOnce(() => ({code: 0}));
diffPackagesMock.mockImplementationOnce(() => 'some-file-name.js\n');
publishNightlyForEachChangedPackage(nightlyVersion);
// ensure we set the version of the last published nightly (for diffing)
expect(writeFileSyncMock.mock.calls[0][1]).toBe(
'{\n "name": "packageA",\n "version": "published-nightly-version"\n}\n',
);
expect(diffPackagesMock).toBeCalledWith(
'packageA@nightly',
'packageA-published-nightly-version.tgz',
{
cwd: '/some/place/packageA',
},
);
// when determining that we DO want to publish, ensure we update the version to the provded nightly version we want to use
expect(writeFileSyncMock.mock.calls[1][1]).toBe(
`{\n "name": "packageA",\n "version": "${nightlyVersion}"\n}\n`,
);
expect(publishPackageMock).toBeCalled();
});
it('doesnt publish because no changes', () => {
const nightlyVersion = '0.73.0-nightly-202108-shortcommit';
diffPackagesMock.mockImplementationOnce(() => '\n');
publishNightlyForEachChangedPackage(nightlyVersion);
expect(writeFileSyncMock.mock.calls[0][1]).toBe(
'{\n "name": "packageA",\n "version": "published-nightly-version"\n}\n',
);
// in this test, we expect there to be no differences between last published nightly and local
// so we never update the version and we don't publish
expect(writeFileSyncMock.mock.calls.length).toBe(1);
expect(publishPackageMock).not.toBeCalled();
});
});
@@ -0,0 +1,135 @@
/**
* 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
* @oncall react_native
*/
const forEachPackage = require('./for-each-package');
const {rm} = require('shelljs');
const {
getPackageVersionStrByTag,
diffPackages,
publishPackage,
pack,
} = require('../npm-utils');
const {restore} = require('../scm-utils');
const path = require('path');
const {writeFileSync} = require('fs');
function hasChanges(
currentNightlyVersion,
packageManifest,
packageAbsolutePath,
) {
// Set local package to same nightly version so diff is comparable
writeFileSync(
path.join(packageAbsolutePath, 'package.json'),
JSON.stringify(
{...packageManifest, version: currentNightlyVersion},
null,
2,
) + '\n',
'utf-8',
);
// prepare local package
pack(packageAbsolutePath);
// ex. react-native-codegen-0.73.0-nightly-20230530-730ca3540.tgz
const localTarballName = `${packageManifest.name
.replace('@', '')
.replace('/', '-')}-${currentNightlyVersion}.tgz`;
// npm diff --diff=<package-name>@nightly --diff=. --diff-name-only
const diff = diffPackages(
`${packageManifest.name}@nightly`,
localTarballName,
{
cwd: packageAbsolutePath,
},
);
// delete tarball and restore package.json changes
rm({cwd: packageAbsolutePath}, localTarballName);
restore(packageAbsolutePath);
return diff.trim().length !== 0;
}
/**
* Publish nightlies for monorepo packages that changed since last publish.
* This is called by `react-native`'s nightly job.
*
* Note: This does not publish `package/react-native`'s nightly. That is handled in `publish-npm`.
*/
function publishNightlyForEachChangedPackage(nightlyVersion) {
forEachPackage(
(packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => {
if (packageManifest.private) {
console.log(`\u23ED Skipping private package ${packageManifest.name}`);
return;
}
let lastPublishedNightlyVersion;
let shouldPublishNightly = false;
console.log(
`\n---- Attempting to publish nightly for ${packageManifest.name}`,
);
try {
lastPublishedNightlyVersion = getPackageVersionStrByTag(
packageManifest.name,
'nightly',
);
shouldPublishNightly = hasChanges(
lastPublishedNightlyVersion,
packageManifest,
packageAbsolutePath,
);
} catch (e) {
console.error(
`Unable to verify if ${packageManifest.name} has changes due to error:`,
);
console.error(e.message);
console.log(`\u23ED Skipping package ${packageManifest.name}`);
return;
}
if (!shouldPublishNightly) {
console.log(
`Detected no changes in ${packageManifest.name}@nightly since last publish. Skipping.`,
);
return;
}
// Set the local package to the updated nightly version
writeFileSync(
path.join(packageAbsolutePath, 'package.json'),
JSON.stringify({...packageManifest, version: nightlyVersion}, null, 2) +
'\n',
'utf-8',
);
const result = publishPackage(packageAbsolutePath, {
tag: 'nightly',
otp: process.env.NPM_CONFIG_OTP,
});
if (result.code !== 0) {
console.log(
`\u274c Failed to publish version ${nightlyVersion} of ${packageManifest.name}. npm publish exited with code ${result.code}:`,
);
console.error(result.stderr);
} else {
console.log(
`\u2705 Successfully published new version of ${packageManifest.name}`,
);
}
},
);
}
module.exports = publishNightlyForEachChangedPackage;
+29 -2
View File
@@ -12,10 +12,10 @@
const {exec} = require('shelljs');
function getPackageVersionStrByTag(packageName, tag) {
const result = exec(`npm view ${packageName}@${tag} version`);
const result = exec(`npm view ${packageName}@${tag} version`, {silent: true});
if (result.code) {
throw `Failed to get ${tag} version from npm\n${result.stderr}`;
throw new Error(`Failed to get ${tag} version from npm\n${result.stderr}`);
}
return result.stdout.trim();
}
@@ -31,7 +31,34 @@ function publishPackage(packagePath, packageOptions, execOptions) {
return exec(`npm publish${tagFlag}${otpFlag}`, options);
}
function diffPackages(packageSpecA, packageSpecB, options) {
const result = exec(
`npm diff --diff=${packageSpecA} --diff=${packageSpecB} --diff-name-only`,
options,
);
if (result.code) {
throw new Error(
`Failed to diff ${packageSpecA} and ${packageSpecB}\n${result.stderr}`,
);
}
return result.stdout;
}
function pack(packagePath) {
const result = exec('npm pack', {
cwd: packagePath,
});
if (result.code !== 0) {
throw new Error(result.stderr);
}
}
module.exports = {
getPackageVersionStrByTag,
publishPackage,
diffPackages,
pack,
};
+8
View File
@@ -17,6 +17,7 @@ const {
getCurrentCommit,
isTaggedLatest,
} = require('./scm-utils');
const publishNightlyForEachChangedPackage = require('./monorepo/publish-nightly-for-each-changed-package');
const {
generateAndroidArtifacts,
publishAndroidArtifactsToMaven,
@@ -136,6 +137,7 @@ function publishNpm(buildType) {
// Set version number in various files (package.json, gradle.properties etc)
// For non-nightly, non-dry-run, CircleCI job `prepare_package_for_release` does this
if (buildType === 'nightly' || buildType === 'dry-run') {
// Sets the version for package/react-native
if (
exec(
`node scripts/set-rn-version.js --to-version ${version} --build-type ${buildType}`,
@@ -146,6 +148,11 @@ function publishNpm(buildType) {
}
}
// set and publish the relevant monorepo packages
if (buildType === 'nightly') {
publishNightlyForEachChangedPackage(version);
}
generateAndroidArtifacts(version);
// Write version number to the build folder
@@ -166,6 +173,7 @@ function publishNpm(buildType) {
tag,
otp: process.env.NPM_CONFIG_OTP,
});
if (result.code) {
echo('Failed to publish package to npm');
return exit(1);
+14
View File
@@ -85,6 +85,19 @@ function revertFiles(filePaths, tmpFolder) {
}
}
// git restore for local path
function restore(repoPath) {
const result = exec('git restore .', {
cwd: repoPath,
});
if (result.code !== 0) {
throw new Error(result.stderr);
}
return;
}
module.exports = {
exitIfNotOnGit,
getCurrentCommit,
@@ -92,4 +105,5 @@ module.exports = {
isTaggedLatest,
revertFiles,
saveFiles,
restore,
};