diff --git a/.github/workflow-scripts/__tests__/generateChangelog-test.js b/.github/workflow-scripts/__tests__/generateChangelog-test.js deleted file mode 100644 index 346aa06cc70..00000000000 --- a/.github/workflow-scripts/__tests__/generateChangelog-test.js +++ /dev/null @@ -1,251 +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 { - generateChangelog, - _computePreviousVersionFrom, - _generateChangelog, - _pushCommit, - _createPR, -} = require('../generateChangelog'); - -const silence = () => {}; -const mockGetNpmPackageInfo = jest.fn(); -const mockExecSync = jest.fn(); -const mockRun = jest.fn(); -const mockFetch = jest.fn(); -const mockExit = jest.fn(); - -jest.mock('../utils.js', () => ({ - log: silence, - run: mockRun, - getNpmPackageInfo: mockGetNpmPackageInfo, -})); - -process.exit = mockExit; -global.fetch = mockFetch; - -describe('Generate Changelog', () => { - beforeEach(jest.clearAllMocks); - - describe('_computePreviousVersionFrom', () => { - it('returns rc.0 when rc is 1', async () => { - const currentVersion = '0.78.0-rc.1'; - const expectedVersion = '0.78.0-rc.0'; - - const receivedVersion = await _computePreviousVersionFrom(currentVersion); - - expect(receivedVersion).toEqual(expectedVersion); - }); - - it('returns previous rc version when rc is > 1', async () => { - const currentVersion = '0.78.0-rc.5'; - const expectedVersion = '0.78.0-rc.4'; - - const receivedVersion = await _computePreviousVersionFrom(currentVersion); - - expect(receivedVersion).toEqual(expectedVersion); - }); - - it('returns previous patch version when rc is 0', async () => { - const currentVersion = '0.78.0-rc.0'; - const expectedVersion = '0.77.1'; - - mockGetNpmPackageInfo.mockReturnValueOnce( - Promise.resolve({version: '0.77.1'}), - ); - - const receivedVersion = await _computePreviousVersionFrom(currentVersion); - - expect(receivedVersion).toEqual(expectedVersion); - }); - - it('returns patch 0 when patch is 1', async () => { - const currentVersion = '0.78.1'; - const expectedVersion = '0.78.0'; - - const receivedVersion = await _computePreviousVersionFrom(currentVersion); - - expect(receivedVersion).toEqual(expectedVersion); - }); - - it('returns previous patch when patch is > 1', async () => { - const currentVersion = '0.78.5'; - const expectedVersion = '0.78.4'; - - const receivedVersion = await _computePreviousVersionFrom(currentVersion); - - expect(receivedVersion).toEqual(expectedVersion); - }); - - it('returns null when patch is 0', async () => { - const currentVersion = '0.78.0'; - - const receivedVersion = await _computePreviousVersionFrom(currentVersion); - - expect(receivedVersion).toBeNull(); - }); - - it("throws an error when the version can't be parsed", async () => { - const currentVersion = '0.78.0-rc0'; - - await expect( - _computePreviousVersionFrom(currentVersion), - ).rejects.toThrow(); - }); - }); - - describe('_generateChangelog', () => { - it('calls git in the right order', async () => { - const currentVersion = '0.79.0-rc5'; - const previousVersion = '0.79.0-rc4'; - const token = 'token'; - - expectedCommandArgs = [ - '@rnx-kit/rn-changelog-generator', - '--base', - `v${previousVersion}`, - '--compare', - `v${currentVersion}`, - '--repo', - '.', - '--changelog', - './CHANGELOG.md', - '--token', - `${token}`, - ]; - - _generateChangelog(previousVersion, currentVersion, token); - - expect(mockRun).toHaveBeenCalledTimes(4); - expect(mockRun).toHaveBeenNthCalledWith(1, 'git checkout main'); - expect(mockRun).toHaveBeenNthCalledWith(2, 'git fetch'); - expect(mockRun).toHaveBeenNthCalledWith(3, 'git pull origin main'); - expect(mockRun).toHaveBeenNthCalledWith( - 4, - `npx ${expectedCommandArgs.join(' ')}`, - ); - }); - }); - - describe('_pushCommit', () => { - it('calls git in the right order', async () => { - const currentVersion = '0.79.0-rc5'; - - _pushCommit(currentVersion); - - expect(mockRun).toHaveBeenCalledTimes(4); - expect(mockRun).toHaveBeenNthCalledWith( - 1, - `git checkout -b changelog/v${currentVersion}`, - ); - expect(mockRun).toHaveBeenNthCalledWith(2, 'git add CHANGELOG.md'); - expect(mockRun).toHaveBeenNthCalledWith( - 3, - `git commit -m "[RN][Changelog] Add changelog for v${currentVersion}"`, - ); - expect(mockRun).toHaveBeenNthCalledWith( - 4, - `git push origin changelog/v${currentVersion}`, - ); - }); - }); - - describe('_createPR', () => { - it('throws error when status is not 201', async () => { - const currentVersion = '0.79.0-rc5'; - const token = 'token'; - - mockFetch.mockReturnValueOnce(Promise.resolve({status: 401})); - - const headers = { - Accept: 'Accept: application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - Authorization: `Bearer ${token}`, - }; - - const content = ` -## Summary -Add Changelog for ${currentVersion} - -## Changelog: -[Internal] - Add Changelog for ${currentVersion} - -## Test Plan: -N/A`; - - const body = { - title: `[RN][Changelog] Add changelog for v${currentVersion}`, - head: `changelog/v${currentVersion}`, - base: 'main', - body: content, - }; - - await expect(_createPR(currentVersion, token)).rejects.toThrow(); - - expect(mockFetch).toHaveBeenCalledTimes(1); - expect(mockFetch).toHaveBeenCalledWith( - 'https://api.github.com/repos/facebook/react-native/pulls', - { - method: 'POST', - headers: headers, - body: JSON.stringify(body), - }, - ); - }); - it('Returns the pr url', async () => { - const currentVersion = '0.79.0-rc5'; - const token = 'token'; - const expectedPrURL = - 'https://github.com/facebook/react-native/pulls/1234'; - - const returnedObject = { - status: 201, - json: () => Promise.resolve({html_url: expectedPrURL}), - }; - mockFetch.mockReturnValueOnce(Promise.resolve(returnedObject)); - - const headers = { - Accept: 'Accept: application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - Authorization: `Bearer ${token}`, - }; - - const content = ` -## Summary -Add Changelog for ${currentVersion} - -## Changelog: -[Internal] - Add Changelog for ${currentVersion} - -## Test Plan: -N/A`; - - const body = { - title: `[RN][Changelog] Add changelog for v${currentVersion}`, - head: `changelog/v${currentVersion}`, - base: 'main', - body: content, - }; - - const receivedPrURL = await _createPR(currentVersion, token); - - expect(mockFetch).toHaveBeenCalledTimes(1); - expect(mockFetch).toHaveBeenCalledWith( - 'https://api.github.com/repos/facebook/react-native/pulls', - { - method: 'POST', - headers: headers, - body: JSON.stringify(body), - }, - ); - expect(receivedPrURL).toEqual(expectedPrURL); - }); - }); -}); diff --git a/.github/workflow-scripts/generateChangelog.js b/.github/workflow-scripts/generateChangelog.js deleted file mode 100644 index 2b01a5f8ccb..00000000000 --- a/.github/workflow-scripts/generateChangelog.js +++ /dev/null @@ -1,120 +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 {log, getNpmPackageInfo, run} = require('./utils'); - -async function _computePreviousVersionFrom(version) { - log(`Computing previous version from: ${version}`); - const regex = /^0\.(\d+)\.(\d+)(-rc\.(\d+))?$/; - const match = version.match(regex); - if (!match) { - throw new Error(`Invalid version format: ${version}`); - } - - const minor = match[1]; - const patch = match[2]; - const rc = match[4]; - - if (rc) { - if (Number(rc) > 0) { - return `0.${minor}.${patch}-rc.${Number(rc) - 1}`; - } - //fetch latest version on NPM - const latestPkg = await getNpmPackageInfo('react-native', 'latest'); - return latestPkg.version; - } else { - if (Number(patch) === 0) { - // No need to generate the changelog for 0.X.0 as we already generated it from RCs - log( - `Skipping changelog generation for ${version} as we already have it from the RCs`, - ); - return null; - } - return `0.${minor}.${Number(patch) - 1}`; - } -} - -function _generateChangelog(previousVersion, version, token) { - log(`Generating changelog for ${version} from ${previousVersion}`); - run('git checkout main'); - run('git fetch'); - run('git pull origin main'); - const generateChangelogCommand = `npx @rnx-kit/rn-changelog-generator --base v${previousVersion} --compare v${version} --repo . --changelog ./CHANGELOG.md --token ${token}`; - run(generateChangelogCommand); -} - -function _pushCommit(version) { - log(`Pushing commit to changelog/v${version}`); - run(`git checkout -b changelog/v${version}`); - run('git add CHANGELOG.md'); - run(`git commit -m "[RN][Changelog] Add changelog for v${version}"`); - run(`git push origin changelog/v${version}`); -} - -async function _createPR(version, token) { - log('Creating changelog pr'); - const url = 'https://api.github.com/repos/facebook/react-native/pulls'; - const body = ` -## Summary -Add Changelog for ${version} - -## Changelog: -[Internal] - Add Changelog for ${version} - -## Test Plan: -N/A`; - - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'Accept: application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ - title: `[RN][Changelog] Add changelog for v${version}`, - head: `changelog/v${version}`, - base: 'main', - body: body, - }), - }); - - if (response.status !== 201) { - throw new Error( - `Failed to create PR: ${response.status} ${response.statusText}`, - ); - } - - const data = await response.json(); - return data.html_url; -} - -async function generateChangelog(version, token) { - if (version.startsWith('v')) { - version = version.substring(1); - } - - const previousVersion = await _computePreviousVersionFrom(version); - if (previousVersion) { - log(`Previous version is ${previousVersion}`); - _generateChangelog(previousVersion, version, token); - _pushCommit(version); - const prURL = await _createPR(version, token); - log(`Created PR: ${prURL}`); - } -} - -module.exports = { - generateChangelog, - // Exported only for testing purposes: - _computePreviousVersionFrom, - _generateChangelog, - _pushCommit, - _createPR, -}; diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml deleted file mode 100644 index ff8ee8d76de..00000000000 --- a/.github/workflows/generate-changelog.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Generate Changelog - -on: - workflow_call: - -jobs: - generate-changelog: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }} - fetch-depth: 0 - fetch-tags: true - - name: Install dependencies - uses: ./.github/actions/yarn-install - - name: Configure Git - shell: bash - run: | - git config --local user.email "bot@reactnative.dev" - git config --local user.name "React Native Bot" - - name: Generate Changelog - uses: actions/github-script@v8 - with: - script: | - const {generateChangelog} = require('./.github/workflow-scripts/generateChangelog'); - const version = '${{ github.ref_name }}'; - await generateChangelog(version, '${{secrets.REACT_NATIVE_BOT_GITHUB_TOKEN}}'); diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index a3e097774b3..4326c0f047f 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -290,19 +290,7 @@ jobs: const version = "${{ github.ref_name }}"; await verifyArtifactsAreOnMaven(version); - # ─── Release-only: changelog, podfile bump, draft release ──────── - generate_changelog: - needs: [determine_mode, publish_react_native] - # always() + explicit result checks: run for a successful release publish - # even when skipped upstream jobs would trip the implicit success() gate. - if: | - always() && - needs.determine_mode.result == 'success' && - needs.publish_react_native.result == 'success' && - needs.determine_mode.outputs.mode == 'release' - uses: ./.github/workflows/generate-changelog.yml - secrets: inherit - + # ─── Release-only: podfile bump, draft release ──────── bump_podfile_lock: needs: [determine_mode, publish_react_native] # always() + explicit result checks: run for a successful release publish @@ -316,13 +304,13 @@ jobs: secrets: inherit create_draft_release: - needs: [determine_mode, generate_changelog, set_hermes_version] + needs: [determine_mode, publish_react_native, set_hermes_version] # always() + explicit result checks: run for a successful release even when # skipped upstream jobs would trip the implicit success() gate. if: | always() && needs.determine_mode.result == 'success' && - needs.generate_changelog.result == 'success' && + needs.publish_react_native.result == 'success' && needs.set_hermes_version.result == 'success' && needs.determine_mode.outputs.mode == 'release' uses: ./.github/workflows/create-draft-release.yml