Skip to content

Commit

Permalink
Replace sh scripts with tested JS scripts to release template
Browse files Browse the repository at this point in the history
The previous scripts to trigger the @react-native-communty/template
release workflow has not been working. This is a rewrite is js, along
with some testing to make this more robust.

TODO:
- Still needs to be used in the GH release workflow.
- Template release workflow needs to land the dry_run input change.

Changelog: [Internal]
  • Loading branch information
blakef committed Sep 6, 2024
1 parent dda3397 commit 3af36e0
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 0 deletions.
122 changes: 122 additions & 0 deletions .github/workflow-scripts/__tests__/publishTemplate-test.js
Original file line number Diff line number Diff line change
@@ -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.
*
* @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.yml',
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.yml',
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 but skips if 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.77.0';
await verifyPublishedTemplate(version);

expect(mockGetNpmPackageInfo).toHaveBeenLastCalledWith('@react-native-community/template', version);
});

it("waits on npm updating version and latest tag", async () => {
const version = '0.77.0';
mockGetNpmPackageInfo
// template@<version>
.mockReturnValueOnce(Promise.reject('mock http/404'))
.mockReturnValueOnce(Promise.resolve())
// template@latest != version
.mockReturnValueOnce(Promise.resolve({ version: '0.76.5' }))
// template@latest == version
.mockReturnValueOnce(Promise.resolve({ version }));
mockSleep
.mockReturnValueOnce(Promise.resolve())
.mockReturnValueOnce(Promise.resolve())
.mockImplementation(() => { throw new Error('Should not be called again!') });

await verifyPublishedTemplate(version, true);

expect(mockGetNpmPackageInfo).toHaveBeenCalledWith('@react-native-community/template', version);
expect(mockGetNpmPackageInfo).toHaveBeenCalledWith('@react-native-community/template', 'latest');
});
});
89 changes: 89 additions & 0 deletions .github/workflow-scripts/publishTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* 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/

/**
* Create a Github Action to publish the community template matching the released version
* of React Native.
*/
module.exports.publishTemplate = async (
github,
version, // 0.75.0-rc.0, note no 'v' prefix
dryRun = true,
) => {
log(`📤 Get the ${TEMPLATE_NPM_PKG} repo to publish ${version}`);

const commitMessage = run('git log -n1 --pretty=%B');
const isLatest = TAG_AS_LATEST_REGEX.test(commitMessage);

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.yml',
ref,
inputs: {
dry_run: dryRun,
is_latest_on_npm: isLatest,
version,
},
});
};

const SLEEP_S = 10;
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) => {
log(`🔍 Is ${TEMPLATE_NPM_PKG}@${version} on npm?`);

while (true) {
try {
await getNpmPackageInfo(TEMPLATE_NPM_PKG, version);
log(`🎉 Found ${TEMPLATE_NPM_PKG}@${version} on npm`);
break;
} catch(e) {
log(`Nope, fetch failed: ${e.message}`);
}
await sleep(SLEEP_S);
}

log(`🔍 Does latest → ${version} on npm?`);

while (latest) {
try {
const pkg = await getNpmPackageInfo(TEMPLATE_NPM_PKG, 'latest');
if (pkg.version === version) {
log(`🎉 ${TEMPLATE_NPM_PKG}@latest → ${version} on npm`);
break;
} else {
log(`🐌 ${TEMPLATE_NPM_PKG}@latest → ${pkg.version} on npm, retrying...`);
}
} catch(e) {
log(`🚨 Fetch failed (will retry): ${e.message}`);
}
await sleep(SLEEP_S);
}
};
28 changes: 28 additions & 0 deletions .github/workflow-scripts/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* 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;

await 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);

0 comments on commit 3af36e0

Please sign in to comment.