mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Automate the check for the Release on NPM (#49164)
Summary: One of the steps we perform when doing a release is to run `npm view react-native` to verify that the release has been published and it is available with the right tag. As of today, we check this manually. This change aims at automating this check so that we don't have to do it manually ourselves. ## Changelog: [Internal] - Releases: automate the npm view check Pull Request resolved: https://github.com/facebook/react-native/pull/49164 Test Plan: Created a veriftyReleaseOnNPM-tests.js jest test to verify that the script works fine. <img width="667" alt="Screenshot 2025-02-04 at 15 18 24" src="https://github.com/user-attachments/assets/cf08155f-80da-4e15-a922-5c16f3fd806e" /> Reviewed By: cortinico Differential Revision: D69118622 Pulled By: cipolleschi fbshipit-source-id: a8d40cd2fcb164d8f7174de680b340510f3e8551
This commit is contained in:
committed by
Facebook GitHub Bot
parent
bf4c887e1d
commit
a52f5514ed
@@ -15,12 +15,14 @@ const {
|
||||
const mockRun = jest.fn();
|
||||
const mockSleep = jest.fn();
|
||||
const mockGetNpmPackageInfo = jest.fn();
|
||||
const mockVerifyPublishedPackage = jest.fn();
|
||||
const silence = () => {};
|
||||
|
||||
jest.mock('../utils.js', () => ({
|
||||
log: silence,
|
||||
run: mockRun,
|
||||
sleep: mockSleep,
|
||||
verifyPublishedPackage: mockVerifyPublishedPackage,
|
||||
getNpmPackageInfo: mockGetNpmPackageInfo,
|
||||
}));
|
||||
|
||||
@@ -82,77 +84,43 @@ describe('#verifyPublishedTemplate', () => {
|
||||
|
||||
it("waits on npm updating for version and not 'latest'", async () => {
|
||||
const NOT_LATEST = false;
|
||||
mockGetNpmPackageInfo
|
||||
// template@<version>
|
||||
.mockReturnValueOnce(Promise.reject('mock http/404'))
|
||||
.mockReturnValueOnce(Promise.resolve());
|
||||
mockSleep.mockReturnValueOnce(Promise.resolve()).mockImplementation(() => {
|
||||
throw new Error('Should not be called again!');
|
||||
});
|
||||
|
||||
const version = '0.77.0';
|
||||
|
||||
await verifyPublishedTemplate(version, NOT_LATEST);
|
||||
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenLastCalledWith(
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
|
||||
'@react-native-community/template',
|
||||
version,
|
||||
null,
|
||||
18,
|
||||
);
|
||||
});
|
||||
|
||||
it('waits on npm updating version and latest tag', async () => {
|
||||
const IS_LATEST = true;
|
||||
const version = '0.77.0';
|
||||
mockGetNpmPackageInfo
|
||||
// template@latest → unknown tag
|
||||
.mockReturnValueOnce(Promise.reject('mock http/404'))
|
||||
// template@latest != version → old tag
|
||||
.mockReturnValueOnce(Promise.resolve({version: '0.76.5'}))
|
||||
// template@latest == version → correct tag
|
||||
.mockReturnValueOnce(Promise.resolve({version}));
|
||||
mockSleep
|
||||
.mockReturnValueOnce(Promise.resolve())
|
||||
.mockReturnValueOnce(Promise.resolve())
|
||||
.mockImplementation(() => {
|
||||
throw new Error('Should not be called again!');
|
||||
});
|
||||
|
||||
await verifyPublishedTemplate(version, IS_LATEST);
|
||||
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledWith(
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
|
||||
'@react-native-community/template',
|
||||
version,
|
||||
'latest',
|
||||
18,
|
||||
);
|
||||
});
|
||||
|
||||
describe('timeouts', () => {
|
||||
let mockProcess;
|
||||
beforeEach(() => {
|
||||
mockProcess = jest.spyOn(process, 'exit').mockImplementation(code => {
|
||||
throw new Error(`process.exit(${code}) called!`);
|
||||
});
|
||||
});
|
||||
afterEach(() => mockProcess.mockRestore());
|
||||
describe('retries', () => {
|
||||
it('will timeout if npm does not update package version after a set number of retries', async () => {
|
||||
const RETRIES = 2;
|
||||
mockGetNpmPackageInfo.mockReturnValue(Promise.reject('mock http/404'));
|
||||
mockSleep.mockReturnValue(Promise.resolve());
|
||||
await expect(() =>
|
||||
verifyPublishedTemplate('0.77.0', true, RETRIES),
|
||||
).rejects.toThrowError('process.exit(1) called!');
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
|
||||
});
|
||||
|
||||
it('will timeout if npm does not update latest tag after a set number of retries', async () => {
|
||||
const RETRIES = 7;
|
||||
const IS_LATEST = true;
|
||||
mockGetNpmPackageInfo.mockReturnValue(
|
||||
Promise.resolve({version: '0.76.5'}),
|
||||
);
|
||||
mockSleep.mockReturnValue(Promise.resolve());
|
||||
await expect(async () => {
|
||||
await verifyPublishedTemplate('0.77.0', IS_LATEST, RETRIES);
|
||||
}).rejects.toThrowError('process.exit(1) called!');
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
|
||||
await verifyPublishedTemplate('0.77.0', true, RETRIES),
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
|
||||
'@react-native-community/template',
|
||||
'0.77.0',
|
||||
'latest',
|
||||
2,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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 {verifyReleaseOnNpm} = require('../verifyReleaseOnNpm');
|
||||
|
||||
const mockVerifyPublishedPackage = jest.fn();
|
||||
const silence = () => {};
|
||||
|
||||
jest.mock('../utils.js', () => ({
|
||||
verifyPublishedPackage: mockVerifyPublishedPackage,
|
||||
}));
|
||||
|
||||
describe('#verifyReleaseOnNPM', () => {
|
||||
beforeEach(jest.clearAllMocks);
|
||||
|
||||
it("waits on npm updating for version and not 'latest'", async () => {
|
||||
const NOT_LATEST = false;
|
||||
const version = '0.78.0';
|
||||
await verifyReleaseOnNpm(version, NOT_LATEST);
|
||||
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenLastCalledWith(
|
||||
'react-native',
|
||||
version,
|
||||
null,
|
||||
18,
|
||||
);
|
||||
});
|
||||
|
||||
it('waits on npm updating version and latest tag', async () => {
|
||||
const IS_LATEST = true;
|
||||
const version = '0.78.0';
|
||||
|
||||
await verifyReleaseOnNpm(version, IS_LATEST);
|
||||
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
|
||||
'react-native',
|
||||
version,
|
||||
'latest',
|
||||
18,
|
||||
);
|
||||
});
|
||||
|
||||
it('waits on npm updating version, not latest and next tag', async () => {
|
||||
const IS_LATEST = false;
|
||||
const version = '0.78.0-rc.0';
|
||||
|
||||
await verifyReleaseOnNpm(version, IS_LATEST);
|
||||
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
|
||||
'react-native',
|
||||
version,
|
||||
'next',
|
||||
18,
|
||||
);
|
||||
});
|
||||
|
||||
it('waits on npm updating version, latest and next tag', async () => {
|
||||
const IS_LATEST = true;
|
||||
const version = '0.78.0-rc.0';
|
||||
|
||||
await verifyReleaseOnNpm(version, IS_LATEST);
|
||||
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
|
||||
'react-native',
|
||||
version,
|
||||
'next',
|
||||
18,
|
||||
);
|
||||
});
|
||||
|
||||
describe('timeouts', () => {
|
||||
it('will timeout if npm does not update package version after a set number of retries', async () => {
|
||||
const RETRIES = 2;
|
||||
|
||||
await verifyReleaseOnNpm('0.77.0', true, RETRIES),
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
|
||||
'react-native',
|
||||
'0.77.0',
|
||||
'latest',
|
||||
2,
|
||||
);
|
||||
});
|
||||
|
||||
it('will timeout if npm does not update latest tag after a set number of retries', async () => {
|
||||
const RETRIES = 7;
|
||||
const IS_LATEST = true;
|
||||
|
||||
await verifyReleaseOnNpm('0.77.0', IS_LATEST, RETRIES);
|
||||
|
||||
expect(mockVerifyPublishedPackage).toHaveBeenCalledWith(
|
||||
'react-native',
|
||||
'0.77.0',
|
||||
'latest',
|
||||
7,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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 {verifyPublishedPackage} = require('../verifyPublishedPackage');
|
||||
|
||||
const mockRun = jest.fn();
|
||||
const mockSleep = jest.fn();
|
||||
const mockGetNpmPackageInfo = jest.fn();
|
||||
const silence = () => {};
|
||||
|
||||
const REACT_NATIVE_PACKAGE = 'react-native';
|
||||
|
||||
jest.mock('../utils.js', () => ({
|
||||
log: silence,
|
||||
run: mockRun,
|
||||
sleep: mockSleep,
|
||||
getNpmPackageInfo: mockGetNpmPackageInfo,
|
||||
}));
|
||||
|
||||
describe('#verifyPublishedPackage', () => {
|
||||
beforeEach(jest.clearAllMocks);
|
||||
|
||||
it("waits on npm updating for version and not 'latest'", async () => {
|
||||
mockGetNpmPackageInfo
|
||||
// template@<version>
|
||||
.mockReturnValueOnce(Promise.reject('mock http/404'))
|
||||
.mockReturnValueOnce(Promise.resolve());
|
||||
mockSleep.mockReturnValueOnce(Promise.resolve()).mockImplementation(() => {
|
||||
throw new Error('Should not be called again!');
|
||||
});
|
||||
|
||||
const version = '0.78.0';
|
||||
await verifyPublishedPackage(REACT_NATIVE_PACKAGE, version, null);
|
||||
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenLastCalledWith(
|
||||
REACT_NATIVE_PACKAGE,
|
||||
version,
|
||||
);
|
||||
});
|
||||
|
||||
it('waits on npm updating version and latest tag', async () => {
|
||||
const version = '0.78.0';
|
||||
mockGetNpmPackageInfo
|
||||
// template@latest → unknown tag
|
||||
.mockReturnValueOnce(Promise.reject('mock http/404'))
|
||||
// template@latest != version → old tag
|
||||
.mockReturnValueOnce(Promise.resolve({version: '0.76.5'}))
|
||||
// template@latest == version → correct tag
|
||||
.mockReturnValueOnce(Promise.resolve({version}));
|
||||
mockSleep
|
||||
.mockReturnValueOnce(Promise.resolve())
|
||||
.mockReturnValueOnce(Promise.resolve())
|
||||
.mockImplementation(() => {
|
||||
throw new Error('Should not be called again!');
|
||||
});
|
||||
|
||||
await verifyPublishedPackage(REACT_NATIVE_PACKAGE, version, 'latest');
|
||||
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledWith(
|
||||
REACT_NATIVE_PACKAGE,
|
||||
'latest',
|
||||
);
|
||||
});
|
||||
|
||||
it('waits on npm updating version and next tag', async () => {
|
||||
const version = '0.78.0-rc.0';
|
||||
mockGetNpmPackageInfo
|
||||
// template@latest → unknown tag
|
||||
.mockReturnValueOnce(Promise.reject('mock http/404'))
|
||||
// template@latest != version → old tag
|
||||
.mockReturnValueOnce(Promise.resolve({version: '0.76.5'}))
|
||||
// template@latest == version → correct tag
|
||||
.mockReturnValueOnce(Promise.resolve({version}));
|
||||
mockSleep
|
||||
.mockReturnValueOnce(Promise.resolve())
|
||||
.mockReturnValueOnce(Promise.resolve())
|
||||
.mockImplementation(() => {
|
||||
throw new Error('Should not be called again!');
|
||||
});
|
||||
debugger;
|
||||
await verifyPublishedPackage(REACT_NATIVE_PACKAGE, version, 'next');
|
||||
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledWith(
|
||||
REACT_NATIVE_PACKAGE,
|
||||
'next',
|
||||
);
|
||||
});
|
||||
|
||||
describe('timeouts', () => {
|
||||
let mockProcess;
|
||||
beforeEach(() => {
|
||||
mockProcess = jest.spyOn(process, 'exit').mockImplementation(code => {
|
||||
throw new Error(`process.exit(${code}) called!`);
|
||||
});
|
||||
});
|
||||
afterEach(() => mockProcess.mockRestore());
|
||||
it('will timeout if npm does not update package version after a set number of retries', async () => {
|
||||
const RETRIES = 2;
|
||||
mockGetNpmPackageInfo.mockReturnValue(Promise.reject('mock http/404'));
|
||||
mockSleep.mockReturnValue(Promise.resolve());
|
||||
await expect(() =>
|
||||
verifyPublishedPackage(
|
||||
REACT_NATIVE_PACKAGE,
|
||||
'0.77.0',
|
||||
'latest',
|
||||
RETRIES,
|
||||
),
|
||||
).rejects.toThrowError('process.exit(1) called!');
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
|
||||
});
|
||||
|
||||
it('will timeout if npm does not update latest tag after a set number of retries', async () => {
|
||||
const RETRIES = 7;
|
||||
const IS_LATEST = true;
|
||||
mockGetNpmPackageInfo.mockReturnValue(
|
||||
Promise.resolve({version: '0.76.5'}),
|
||||
);
|
||||
mockSleep.mockReturnValue(Promise.resolve());
|
||||
await expect(async () => {
|
||||
await verifyPublishedPackage(
|
||||
REACT_NATIVE_PACKAGE,
|
||||
'0.77.0',
|
||||
'latest',
|
||||
RETRIES,
|
||||
);
|
||||
}).rejects.toThrowError('process.exit(1) called!');
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
const {run, sleep, getNpmPackageInfo, log} = require('./utils.js');
|
||||
const {run, sleep, log, verifyPublishedPackage} = require('./utils.js');
|
||||
|
||||
const TAG_AS_LATEST_REGEX = /#publish-packages-to-npm&latest/;
|
||||
|
||||
@@ -53,8 +53,7 @@ module.exports.publishTemplate = async (github, version, dryRun = true) => {
|
||||
});
|
||||
};
|
||||
|
||||
const SLEEP_S = 10;
|
||||
const MAX_RETRIES = 3 * 6; // 3 minutes
|
||||
const MAX_RETRIES = 3 * 6; // 18 attempts. Waiting between attempt: 10 s. Total time: 3 mins.
|
||||
const TEMPLATE_NPM_PKG = '@react-native-community/template';
|
||||
|
||||
/**
|
||||
@@ -68,36 +67,15 @@ module.exports.verifyPublishedTemplate = async (
|
||||
latest = false,
|
||||
retries = MAX_RETRIES,
|
||||
) => {
|
||||
log(`🔍 Is ${TEMPLATE_NPM_PKG}@${version} on npm?`);
|
||||
|
||||
let count = retries;
|
||||
while (count-- > 0) {
|
||||
try {
|
||||
const json = await getNpmPackageInfo(
|
||||
TEMPLATE_NPM_PKG,
|
||||
latest ? 'latest' : version,
|
||||
);
|
||||
log(`🎉 Found ${TEMPLATE_NPM_PKG}@${version} on npm`);
|
||||
if (!latest) {
|
||||
return;
|
||||
}
|
||||
if (json.version === version) {
|
||||
log(`🎉 ${TEMPLATE_NPM_PKG}@latest → ${version} on npm`);
|
||||
return;
|
||||
}
|
||||
log(
|
||||
`🐌 ${TEMPLATE_NPM_PKG}@latest → ${pkg.version} on npm and not ${version} as expected, retrying...`,
|
||||
);
|
||||
} catch (e) {
|
||||
log(`Nope, fetch failed: ${e.message}`);
|
||||
}
|
||||
await sleep(SLEEP_S);
|
||||
try {
|
||||
await verifyPublishedPackage(
|
||||
TEMPLATE_NPM_PKG,
|
||||
version,
|
||||
latest ? 'latest' : null,
|
||||
retries,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let msg = `🚨 Timed out when trying to verify ${TEMPLATE_NPM_PKG}@${version} on npm`;
|
||||
if (latest) {
|
||||
msg += ' and latest tag points to this version.';
|
||||
}
|
||||
log(msg);
|
||||
process.exit(1);
|
||||
};
|
||||
|
||||
@@ -12,18 +12,22 @@ const {execSync} = require('child_process');
|
||||
function run(cmd) {
|
||||
return execSync(cmd, 'utf8').toString().trim();
|
||||
}
|
||||
module.exports.run = run;
|
||||
|
||||
async function sleep(seconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
||||
}
|
||||
module.exports.sleep = sleep;
|
||||
|
||||
async function getNpmPackageInfo(pkg, versionOrTag) {
|
||||
return fetch(`https://registry.npmjs.org/${pkg}/${versionOrTag}`).then(resp =>
|
||||
resp.json(),
|
||||
);
|
||||
}
|
||||
module.exports.getNpmPackageInfo = getNpmPackageInfo;
|
||||
|
||||
module.exports.log = (...args) => console.log(...args);
|
||||
const log = (...args) => console.log(...args);
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
getNpmPackageInfo,
|
||||
sleep,
|
||||
run,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 {log, getNpmPackageInfo, sleep} = require('./utils');
|
||||
|
||||
const SLEEP_S = 10;
|
||||
const MAX_RETRIES = 3 * 6; // 18 attempts. Waiting between attempt: 10 s. Total time: 3 mins.
|
||||
|
||||
async function verifyPublishedPackage(
|
||||
packageName,
|
||||
version,
|
||||
tag = null,
|
||||
retries = MAX_RETRIES,
|
||||
) {
|
||||
debugger;
|
||||
log(`🔍 Is ${packageName}@${version} on npm?`);
|
||||
|
||||
let count = retries;
|
||||
while (count-- > 0) {
|
||||
try {
|
||||
const json = await getNpmPackageInfo(packageName, tag ? tag : version);
|
||||
log(`🎉 Found ${packageName}@${version} on npm`);
|
||||
if (!tag) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for next tag
|
||||
if (tag === 'next' && json.version === version) {
|
||||
log(`🎉 ${packageName}@next → ${version} on npm`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for latest tag
|
||||
if (tag === 'latest' && json.version === version) {
|
||||
log(`🎉 ${packageName}@latest → ${version} on npm`);
|
||||
return;
|
||||
}
|
||||
|
||||
log(
|
||||
`🐌 ${packageName}@${tag} → ${pkg.version} on npm and not ${version} as expected, retrying...`,
|
||||
);
|
||||
} catch (e) {
|
||||
log(`Nope, fetch failed: ${e.message}`);
|
||||
}
|
||||
await sleep(SLEEP_S);
|
||||
}
|
||||
|
||||
let msg = `🚨 Timed out when trying to verify ${packageName}@${version} on npm`;
|
||||
if (tag) {
|
||||
msg += ` and ${tag} tag points to this version.`;
|
||||
}
|
||||
log(msg);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
verifyPublishedPackage,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 {run, sleep, log, verifyPublishedPackage} = require('./utils.js');
|
||||
const REACT_NATIVE_NPM_PKG = 'react-native';
|
||||
const MAX_RETRIES = 3 * 6; // 18 attempts. Waiting between attempt: 10 s. Total time: 3 mins.
|
||||
/**
|
||||
* Will verify that @latest, @next and the @<version> have been published.
|
||||
*
|
||||
* NOTE: This will infinitely query each step until successful, make sure the
|
||||
* calling job has a timeout.
|
||||
*/
|
||||
module.exports.verifyReleaseOnNpm = async (
|
||||
version,
|
||||
latest = false,
|
||||
retries = MAX_RETRIES,
|
||||
) => {
|
||||
const tag = version.includes('-rc.') ? 'next' : latest ? 'latest' : null;
|
||||
await verifyPublishedPackage(REACT_NATIVE_NPM_PKG, version, tag, retries);
|
||||
};
|
||||
Reference in New Issue
Block a user