diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index cdbf17e5ddc..97a8a23463b 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -78,7 +78,7 @@ runs: shell: bash run: | previousCommit=${{ github.event.before }} - baseRef=${{ inputs.workflow_name == 'process-release' && 'main' || github.base_ref }} + baseRef=${{ github.base_ref }} origin=$( [[ -z $baseRef ]] && echo $previousCommit || echo "origin/$baseRef" ) yarn workspace scripts setRunVariables "$origin" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b0fda5f0ded..cddd95fedee 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -230,7 +230,7 @@ jobs: run: yarn cli cts run ${{ matrix.client.language }} - name: Zip artifact before storing - run: zip -q -r clients-${{ matrix.client.language }}.zip ${{ matrix.client.path }} ${{ matrix.client.testsOutputPath }} -x "${{ matrix.client.path }}/node_modules/.**" + run: zip -r -y clients-${{ matrix.client.language }}.zip ${{ matrix.client.path }} ${{ matrix.client.testsOutputPath }} -x "**/node_modules/**" "**/node_modules/.**" "clients/algoliasearch-client-javascript/.yarn/**" - name: Store ${{ matrix.client.language }} clients uses: actions/upload-artifact@v3 diff --git a/package.json b/package.json index ccf8efb422c..ce2ac2ca664 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "github-actions:lint": "eslint --ext=yml .github/", "postinstall": "husky install && yarn workspace eslint-plugin-automation-custom build", "playground:browser": "yarn workspace javascript-browser-playground start", - "release": "yarn workspace scripts createReleaseIssue", + "release": "yarn workspace scripts createReleasePR", "scripts:lint": "eslint --ext=ts scripts/", "scripts:test": "yarn workspace scripts test", "specs:fix": "eslint --ext=yml specs/$0 --fix", diff --git a/scripts/common.ts b/scripts/common.ts index 45d4c209e9a..a2b01bcaa1b 100644 --- a/scripts/common.ts +++ b/scripts/common.ts @@ -272,7 +272,7 @@ export function ensureGitHubToken(): string { export function getOctokit(): Octokit { const token = ensureGitHubToken(); return new Octokit({ - auth: `token ${token}`, + auth: token, }); } diff --git a/scripts/package.json b/scripts/package.json index 9eb37afa2cf..aad518a8246 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -4,7 +4,7 @@ "scripts": { "cleanGeneratedBranch": "ts-node ci/codegen/cleanGeneratedBranch.ts", "createMatrix": "ts-node ci/githubActions/createMatrix.ts", - "createReleaseIssue": "ts-node release/create-release-issue.ts", + "createReleasePR": "ts-node release/createReleasePR.ts", "pre-commit": "./ci/husky/pre-commit.js", "pushGeneratedCode": "ts-node ci/codegen/pushGeneratedCode.ts", "renovateWeeklyPR": "ts-node ci/githubActions/renovateWeeklyPR.ts", diff --git a/scripts/release/__tests__/create-release-issue.test.ts b/scripts/release/__tests__/createReleasePR.test.ts similarity index 67% rename from scripts/release/__tests__/create-release-issue.test.ts rename to scripts/release/__tests__/createReleasePR.test.ts index 1b2fc4c68cb..bff5c8b5881 100644 --- a/scripts/release/__tests__/create-release-issue.test.ts +++ b/scripts/release/__tests__/createReleasePR.test.ts @@ -5,9 +5,9 @@ import { getSkippedCommitsText, decideReleaseStrategy, readVersions, -} from '../create-release-issue'; +} from '../createReleasePR'; -describe('create release issue', () => { +describe('createReleasePR', () => { it('reads versions of the current language', () => { expect(readVersions()).toEqual({ java: { @@ -92,10 +92,10 @@ describe('create release issue', () => { }, }) ).toMatchInlineSnapshot(` - "- javascript: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** - - java: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** - - php: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_**" - `); + "- javascript: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** + - java: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** + - php: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_**" + `); }); it('generates text for version changes with a language with no commit', () => { @@ -118,10 +118,10 @@ describe('create release issue', () => { }, }) ).toMatchInlineSnapshot(` - "- javascript: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** - - java: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** - - ~php: 0.0.1 (no commit)~" - `); + "- javascript: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** + - java: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** + - ~php: 0.0.1 (no commit)~" + `); }); it('generates text for version changes with a language to skip', () => { @@ -144,11 +144,11 @@ describe('create release issue', () => { }, }) ).toMatchInlineSnapshot(` - "- javascript: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** - - ~java: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_**~ - - No \`feat\` or \`fix\` commit, thus unchecked by default. - - php: 0.0.1 -> **\`minor\` _(e.g. 0.1.0)_**" - `); + "- javascript: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_** + - ~java: 0.0.1 -> **\`patch\` _(e.g. 0.0.2)_**~ + - No \`feat\` or \`fix\` commit, thus unchecked by default. + - php: 0.0.1 -> **\`minor\` _(e.g. 0.1.0)_**" + `); }); }); @@ -363,47 +363,117 @@ describe('create release issue', () => { }); }); - it('generates text for skipped commits', () => { - expect( - getSkippedCommitsText({ - commitsWithoutLanguageScope: [], - commitsWithUnknownLanguageScope: [], - }) - ).toMatchInlineSnapshot(`"_(None)_"`); - - expect( - getSkippedCommitsText({ - commitsWithoutLanguageScope: [ - 'abcdefg fix: something', - 'abcdefg fix: somethin2', - ], + describe('getSkippedCommitsText', () => { + it('does not generate text if there is no commits', () => { + expect( + getSkippedCommitsText({ + commitsWithoutLanguageScope: [], + commitsWithUnknownLanguageScope: [], + }) + ).toMatchInlineSnapshot(`"_(None)_"`); + }); - commitsWithUnknownLanguageScope: [ - 'abcdef2 fix(pascal): what', - 'abcdef2 fix(pascal): what is that', - ], - }) - ).toMatchInlineSnapshot(` - "

-

It doesn't mean these commits are being excluded from the release. It means they're not taken into account when the release process figured out the next version number, and updated the changelog.

- -
- - Commits without language scope: - - - - abcdefg fix: something - - abcdefg fix: somethin2 -
- -
- - Commits with unknown language scope: - - - - abcdef2 fix(pascal): what - - abcdef2 fix(pascal): what is that -
" - `); + it('generates text for skipped commits', () => { + expect( + getSkippedCommitsText({ + commitsWithoutLanguageScope: [ + 'abcdefg fix: something', + 'abcdefg fix: somethin2', + ], + + commitsWithUnknownLanguageScope: [ + 'abcdef2 fix(pascal): what', + 'abcdef2 fix(pascal): what is that', + ], + }) + ).toMatchInlineSnapshot(` + " +

It doesn't mean these commits are being excluded from the release. It means they're not taken into account when the release process figured out the next version number, and updated the changelog.

+ +
+ + Commits without language scope: + + + - abcdefg fix: something + - abcdefg fix: somethin2 +
+ +
+ + Commits with unknown language scope: + + + - abcdef2 fix(pascal): what + - abcdef2 fix(pascal): what is that +
" + `); + }); + + it('limits the size of the commits to 15 if there is too many', () => { + const fakeCommitsWithoutLanguageScope: string[] = []; + const fakeCommitsWithUnknownLanguageScope: string[] = []; + + for (let i = 0; i < 100; i++) { + fakeCommitsWithoutLanguageScope.push(`abcdefg${i} fix: something`); + fakeCommitsWithUnknownLanguageScope.push( + `abcdefg${i} fix(pascal): something` + ); + } + + expect( + getSkippedCommitsText({ + commitsWithoutLanguageScope: fakeCommitsWithoutLanguageScope, + commitsWithUnknownLanguageScope: fakeCommitsWithUnknownLanguageScope, + }) + ).toMatchInlineSnapshot(` + " +

It doesn't mean these commits are being excluded from the release. It means they're not taken into account when the release process figured out the next version number, and updated the changelog.

+ +
+ + Commits without language scope: + + + - abcdefg0 fix: something + - abcdefg1 fix: something + - abcdefg2 fix: something + - abcdefg3 fix: something + - abcdefg4 fix: something + - abcdefg5 fix: something + - abcdefg6 fix: something + - abcdefg7 fix: something + - abcdefg8 fix: something + - abcdefg9 fix: something + - abcdefg10 fix: something + - abcdefg11 fix: something + - abcdefg12 fix: something + - abcdefg13 fix: something + - abcdefg14 fix: something +
+ +
+ + Commits with unknown language scope: + + + - abcdefg0 fix(pascal): something + - abcdefg1 fix(pascal): something + - abcdefg2 fix(pascal): something + - abcdefg3 fix(pascal): something + - abcdefg4 fix(pascal): something + - abcdefg5 fix(pascal): something + - abcdefg6 fix(pascal): something + - abcdefg7 fix(pascal): something + - abcdefg8 fix(pascal): something + - abcdefg9 fix(pascal): something + - abcdefg10 fix(pascal): something + - abcdefg11 fix(pascal): something + - abcdefg12 fix(pascal): something + - abcdefg13 fix(pascal): something + - abcdefg14 fix(pascal): something +
" + `); + }); }); }); diff --git a/scripts/release/__tests__/process-release.test.ts b/scripts/release/__tests__/updateAPIVersions.test.ts similarity index 81% rename from scripts/release/__tests__/process-release.test.ts rename to scripts/release/__tests__/updateAPIVersions.test.ts index 379201916eb..de2e8d95293 100644 --- a/scripts/release/__tests__/process-release.test.ts +++ b/scripts/release/__tests__/updateAPIVersions.test.ts @@ -1,8 +1,8 @@ -import { getVersionChangesText } from '../create-release-issue'; -import { getVersionsToRelease } from '../process-release'; +import { getVersionChangesText } from '../createReleasePR'; import TEXT from '../text'; +import { getVersionsToRelease } from '../updateAPIVersions'; -describe('process release', () => { +describe('updateAPIversions', () => { it('gets versions to release', () => { const versions = getVersionsToRelease(` ## Version Changes @@ -20,8 +20,8 @@ describe('process release', () => { expect(versions.php?.releaseType).toEqual('patch'); }); - it('parses issue body correctly', () => { - // This test is a glue between create-release-issue and process-release. + it('correctly reads clients version and their next release type', () => { + // This test is a glue between createReleasePR and updateAPIVersions. const issueBody = [ TEXT.versionChangeHeader, getVersionChangesText({ diff --git a/scripts/release/create-release-issue.ts b/scripts/release/createReleasePR.ts similarity index 87% rename from scripts/release/create-release-issue.ts rename to scripts/release/createReleasePR.ts index 6624ee59219..2504131bde9 100755 --- a/scripts/release/create-release-issue.ts +++ b/scripts/release/createReleasePR.ts @@ -19,7 +19,6 @@ import { import { getPackageVersionDefault } from '../config'; import { RELEASED_TAG } from './common'; -import { processRelease } from './process-release'; import TEXT from './text'; import type { Versions, @@ -29,6 +28,7 @@ import type { Scope, Changelog, } from './types'; +import { updateAPIVersions } from './updateAPIVersions'; dotenv.config({ path: ROOT_ENV_PATH }); @@ -79,7 +79,10 @@ export function getSkippedCommitsText({ return '_(None)_'; } - return `

+ // GitHub API restrict the size of a PR body, if we send too many commits + // we might end up with 502 errors when trying to send the pull request + // So we limit the size of the missed commits + return `

${TEXT.skippedCommitsDesc}

@@ -87,7 +90,10 @@ export function getSkippedCommitsText({ Commits without language scope: - ${commitsWithoutLanguageScope.map((commit) => `- ${commit}`).join('\n')} + ${commitsWithoutLanguageScope + .slice(0, 15) + .map((commit) => `- ${commit}`) + .join('\n')}
@@ -95,7 +101,10 @@ export function getSkippedCommitsText({ Commits with unknown language scope: - ${commitsWithUnknownLanguageScope.map((commit) => `- ${commit}`).join('\n')} + ${commitsWithUnknownLanguageScope + .slice(0, 15) + .map((commit) => `- ${commit}`) + .join('\n')}
`; } @@ -270,7 +279,7 @@ async function getCommits(): Promise<{ }; } -async function createReleaseIssue(): Promise { +async function createReleasePR(): Promise { ensureGitHubToken(); if (!process.env.LOCAL_TEST_DEV) { @@ -336,38 +345,42 @@ async function createReleaseIssue(): Promise { const headBranch = `chore/prepare-release-${TODAY}`; console.log('Updating config files...'); - await processRelease(versionChanges, changelog, headBranch); + await updateAPIVersions(versionChanges, changelog, headBranch); console.log('Creating pull request...'); const octokit = getOctokit(); - try { - const { - data: { number, html_url: url }, - } = await octokit.rest.pulls.create({ - owner: OWNER, - repo: REPO, - title: `chore: prepare release ${TODAY}`, - body: [ - TEXT.header, - TEXT.summary, - TEXT.versionChangeHeader, - versionChanges, - TEXT.skippedCommitsHeader, - skippedCommits, - ].join('\n\n'), - base: 'main', - head: headBranch, - }); - - console.log(`Release PR #${number} is ready for review.`); - console.log(` > ${url}`); - } catch (e) { - throw new Error(`Unable to create the release PR: ${e}`); - } + const { + data: { number, html_url: url }, + } = await octokit.pulls.create({ + owner: OWNER, + repo: REPO, + title: `chore: prepare release ${TODAY}`, + body: [ + TEXT.header, + TEXT.summary, + TEXT.versionChangeHeader, + versionChanges, + TEXT.skippedCommitsHeader, + skippedCommits, + ].join('\n\n'), + base: 'main', + head: headBranch, + }); + + console.log('Assigning team members to the PR...'); + await octokit.pulls.requestReviewers({ + owner: OWNER, + repo: REPO, + pull_number: number, + team_reviewers: ['api-clients-automation'], + }); + + console.log(`Release PR #${number} is ready for review.`); + console.log(` > ${url}`); } // JS version of `if __name__ == '__main__'` if (require.main === module) { - createReleaseIssue(); + createReleasePR(); } diff --git a/scripts/release/process-release.ts b/scripts/release/updateAPIVersions.ts similarity index 92% rename from scripts/release/process-release.ts rename to scripts/release/updateAPIVersions.ts index d639996a735..606f5400c70 100755 --- a/scripts/release/process-release.ts +++ b/scripts/release/updateAPIVersions.ts @@ -174,13 +174,13 @@ export function getVersionsToRelease( } /** - * Updates the changelogs and the config files containing versions of the API clients. + * Updates the changelogs and the config files containing versions of the API clients, then pushes the changes to the `headBranch`. * * @param versionChanges - A summary of the version changes, with their new version. - * @param changelog - The changelog of all the languages, which is generated by `create-release-issue`. + * @param changelog - The changelog of all the languages, which is generated by `createReleasePR`. * @param headBranch - The branch to push the changes to. */ -export async function processRelease( +export async function updateAPIVersions( versionChanges: string, changelog: Changelog, headBranch: string @@ -230,12 +230,17 @@ export async function processRelease( }); } - console.log(`Pushing updated configs to ${headBranch}`); - await run(`git add .`, { verbose: true }); - await run( - `CI=true git commit -m "${headBranch.replace('chore/', 'chore: ')}"`, - { verbose: true } - ); + console.log(`Pushing updated changes to ${headBranch}`); + const commitMessage = headBranch.replace('chore/', 'chore: '); + await run(`git add clients config`, { verbose: true }); + if (process.env.LOCAL_TEST_DEV) { + await run(`CI=true git commit -m "${commitMessage} [skip ci]"`, { + verbose: true, + }); + } else { + await run(`CI=true git commit -m "${commitMessage}"`, { verbose: true }); + } + await run(`git push origin ${headBranch}`, { verbose: true }); await run(`git checkout ${MAIN_BRANCH}`, { verbose: true }); } diff --git a/website/docs/contributing/release-process.md b/website/docs/contributing/release-process.md index 36b87c9d726..73cac73ce55 100644 --- a/website/docs/contributing/release-process.md +++ b/website/docs/contributing/release-process.md @@ -18,24 +18,20 @@ Once setup, you can run: yarn release ``` -It will create [a release issue](https://github.com/algolia/api-clients-automation/issues/407). +It will create [a release PR](https://github.com/algolia/api-clients-automation/pull/545). -## 2. Review the release issue. +## 2. Review the release PR. -You need to review the release issue, in two parts: +You need to review the release PR, in two parts: 1. version changes 2. CHANGELOGs -Any changes applied in the issue will be taken into account by the release process. +You need approval from a member of the [`@algolia/api-clients-automation`](https://github.com/orgs/algolia/teams/api-clients-automation) team to release clients. -You need approval from a member of the [`@algolia/api-clients-automation`](https://github.com/orgs/algolia/teams/api-clients-automation) team to release clients. Commenting "approved" will close the issue and trigger [the release action](#3-the-release-action). +## 3. The release process. -## 3. The release action. - -The [GitHub action release](https://github.com/algolia/api-clients-automation/blob/main/.github/workflows/process-release.yml) is triggered. It generates clients and push changes to each language repository on their `next` branch. - -This part runs conditionally according to what has been done in [the issue review](#2-review-the-release-issue). Under "Version Changes" section of the release issue, if a language is checked, this part will creates a commit like `chore: release v` in each repository. If it is not checked, it will create a commit like `chore: update repo `. +After a full CI run, a release commit will be sent to every repository and spread changes to their `next` branch. Each language repository should have their own release process, and should run only when the latest commit starts with `chore: release`. By doing so, we have a way to just update the repository, for example READMEs, without having to release.