forked from microsoft/react-native-macos
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'facebook/0.76-stable' into almost-0.76-…
…stable
- Loading branch information
Showing
185 changed files
with
62,365 additions
and
44,462 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
.github/workflow-scripts/__tests__/publishTemplate-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
/** | ||
* 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 { | ||
publishTemplate, | ||
verifyPublishedTemplate, | ||
} = require('../publishTemplate'); | ||
|
||
const mockRun = jest.fn(); | ||
const mockSleep = jest.fn(); | ||
const mockGetNpmPackageInfo = jest.fn(); | ||
const silence = () => {}; | ||
|
||
jest.mock('../utils.js', () => ({ | ||
log: silence, | ||
run: mockRun, | ||
sleep: mockSleep, | ||
getNpmPackageInfo: mockGetNpmPackageInfo, | ||
})); | ||
|
||
const getMockGithub = () => ({ | ||
rest: { | ||
actions: { | ||
createWorkflowDispatch: jest.fn(), | ||
}, | ||
}, | ||
}); | ||
|
||
describe('#publishTemplate', () => { | ||
beforeEach(jest.clearAllMocks); | ||
|
||
it('checks commits for magic #publish-package-to-npm&latest string and sets latest', async () => { | ||
mockRun.mockReturnValueOnce(` | ||
The commit message | ||
#publish-packages-to-npm&latest`); | ||
|
||
const github = getMockGithub(); | ||
await publishTemplate(github, '0.76.0', true); | ||
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({ | ||
owner: 'react-native-community', | ||
repo: 'template', | ||
workflow_id: 'release.yaml', | ||
ref: '0.76-stable', | ||
inputs: { | ||
dry_run: true, | ||
is_latest_on_npm: true, | ||
version: '0.76.0', | ||
}, | ||
}); | ||
}); | ||
|
||
it('pubished as is_latest_on_npm = false if missing magic string', async () => { | ||
mockRun.mockReturnValueOnce(` | ||
The commit message without magic | ||
`); | ||
|
||
const github = getMockGithub(); | ||
await publishTemplate(github, '0.76.0', false); | ||
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({ | ||
owner: 'react-native-community', | ||
repo: 'template', | ||
workflow_id: 'release.yaml', | ||
ref: '0.76-stable', | ||
inputs: { | ||
dry_run: false, | ||
is_latest_on_npm: false, | ||
version: '0.76.0', | ||
}, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('#verifyPublishedTemplate', () => { | ||
beforeEach(jest.clearAllMocks); | ||
|
||
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( | ||
'@react-native-community/template', | ||
version, | ||
); | ||
}); | ||
|
||
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( | ||
'@react-native-community/template', | ||
'latest', | ||
); | ||
}); | ||
|
||
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(() => | ||
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); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/** | ||
* 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, getNpmPackageInfo, log} = require('./utils.js'); | ||
|
||
const TAG_AS_LATEST_REGEX = /#publish-packages-to-npm&latest/; | ||
|
||
/** | ||
* Should this commit be `latest` on npm? | ||
*/ | ||
function isLatest() { | ||
const commitMessage = run('git log -n1 --pretty=%B'); | ||
return TAG_AS_LATEST_REGEX.test(commitMessage); | ||
} | ||
module.exports.isLatest = isLatest; | ||
|
||
/** | ||
* Create a Github Action to publish the community template matching the released version | ||
* of React Native. | ||
*/ | ||
module.exports.publishTemplate = async (github, version, dryRun = true) => { | ||
log(`📤 Get the ${TEMPLATE_NPM_PKG} repo to publish ${version}`); | ||
|
||
const is_latest_on_npm = isLatest(); | ||
|
||
const majorMinor = /^v?(\d+\.\d+)/.exec(version); | ||
|
||
if (!majorMinor) { | ||
log(`🔥 can't capture MAJOR.MINOR from '${version}', giving up.`); | ||
process.exit(1); | ||
} | ||
|
||
// MAJOR.MINOR-stable | ||
const ref = `${majorMinor[1]}-stable`; | ||
|
||
await github.rest.actions.createWorkflowDispatch({ | ||
owner: 'react-native-community', | ||
repo: 'template', | ||
workflow_id: 'release.yaml', | ||
ref, | ||
inputs: { | ||
dry_run: dryRun, | ||
is_latest_on_npm, | ||
// 0.75.0-rc.0, note no 'v' prefix | ||
version: version.replace(/^v/, ''), | ||
}, | ||
}); | ||
}; | ||
|
||
const SLEEP_S = 10; | ||
const MAX_RETRIES = 3 * 6; // 3 minutes | ||
const TEMPLATE_NPM_PKG = '@react-native-community/template'; | ||
|
||
/** | ||
* Will verify that @latest 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.verifyPublishedTemplate = async ( | ||
version, | ||
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); | ||
} | ||
|
||
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); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** | ||
* 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 {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 => | ||
res.json(), | ||
); | ||
} | ||
module.exports.getNpmPackageInfo = getNpmPackageInfo; | ||
|
||
module.exports.log = (...args) => console.log(...args); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.