diff --git a/.github/workflows/process-release.yml b/.github/workflows/process-release.yml index 389bcbf49a..a1911ea3e2 100644 --- a/.github/workflows/process-release.yml +++ b/.github/workflows/process-release.yml @@ -26,3 +26,4 @@ jobs: env: EVENT_NUMBER: ${{ github.event.issue.number }} GITHUB_TOKEN: ${{ secrets.TOKEN_RELEASE_BOT }} + VERSION_TAG_ON_RELEASE: $${{ secrets.VERSION_TAG_ON_RELEASE }} diff --git a/scripts/common.ts b/scripts/common.ts index 53ebd7dd07..f2a0dda131 100644 --- a/scripts/common.ts +++ b/scripts/common.ts @@ -13,6 +13,8 @@ export const DOCKER = Boolean(process.env.DOCKER); // This script is run by `yarn workspace ...`, which means the current working directory is `./script` export const ROOT_DIR = path.resolve(process.cwd(), '..'); +export const ROOT_ENV_PATH = path.resolve(ROOT_DIR, '.env'); + export const GENERATORS: Record = { // Default `algoliasearch` package as it's built similarly to generated clients 'javascript-algoliasearch': { @@ -65,7 +67,17 @@ export function splitGeneratorKey(generatorKey: string): Generator { return { language, client, key: generatorKey }; } -export function getGitHubUrl(lang: string): string { +type GitHubUrl = ( + lang: string, + options?: { + token?: string; + } +) => string; + +export const getGitHubUrl: GitHubUrl = ( + lang: string, + { token } = {} +): string => { const entry = Object.entries(openapitools['generator-cli'].generators).find( (_entry) => _entry[0].startsWith(`${lang}-`) ); @@ -74,8 +86,16 @@ export function getGitHubUrl(lang: string): string { throw new Error(`\`${lang}\` is not found from \`openapitools.json\`.`); } const { gitHost, gitRepoId } = entry[1]; - return `https://github.com/${gitHost}/${gitRepoId}`; -} + + // GitHub Action provides a default token for authentication + // https://docs.github.com/en/actions/security-guides/automatic-token-authentication + // But it has access to only the self repository. + // If we want to do something like pushing commits to other repositories, + // we need to specify a token with more access. + return token + ? `https://${token}:${token}@github.com/${gitHost}/${gitRepoId}` + : `https://github.com/${gitHost}/${gitRepoId}`; +}; export function createGeneratorKey({ language, diff --git a/scripts/release/create-release-issue.ts b/scripts/release/create-release-issue.ts index c2dc26288b..be1a01f7c5 100755 --- a/scripts/release/create-release-issue.ts +++ b/scripts/release/create-release-issue.ts @@ -3,12 +3,12 @@ import { Octokit } from '@octokit/rest'; import dotenv from 'dotenv'; import semver from 'semver'; -import { GENERATORS, LANGUAGES, run } from '../common'; +import { GENERATORS, LANGUAGES, ROOT_ENV_PATH, run } from '../common'; import { RELEASED_TAG, MAIN_BRANCH, OWNER, REPO } from './common'; import TEXT from './text'; -dotenv.config(); +dotenv.config({ path: ROOT_ENV_PATH }); type Version = { current: string; diff --git a/scripts/release/process-release.ts b/scripts/release/process-release.ts index b8e4dd819c..6a8559eb91 100755 --- a/scripts/release/process-release.ts +++ b/scripts/release/process-release.ts @@ -5,7 +5,13 @@ import dotenv from 'dotenv'; import execa from 'execa'; import openapitools from '../../openapitools.json'; -import { toAbsolutePath, run, exists, getGitHubUrl } from '../common'; +import { + ROOT_ENV_PATH, + toAbsolutePath, + run, + exists, + getGitHubUrl, +} from '../common'; import { getLanguageFolder } from '../config'; import { @@ -13,12 +19,12 @@ import { OWNER, REPO, getMarkdownSection, - getTargetBranch, getGitAuthor, + getTargetBranch, } from './common'; import TEXT from './text'; -dotenv.config(); +dotenv.config({ path: ROOT_ENV_PATH }); if (!process.env.GITHUB_TOKEN) { throw new Error('Environment variable `GITHUB_TOKEN` does not exist.'); @@ -42,11 +48,18 @@ type VersionsToRelease = { [lang: string]: { current: string; next: string; + dateStamp: string; }; }; +function getDateStamp(): string { + return new Date().toISOString().split('T')[0]; +} + function getVersionsToRelease(issueBody: string): VersionsToRelease { const versionsToRelease: VersionsToRelease = {}; + const dateStamp = getDateStamp(); + getMarkdownSection(issueBody, TEXT.versionChangeHeader) .split('\n') .forEach((line) => { @@ -58,6 +71,7 @@ function getVersionsToRelease(issueBody: string): VersionsToRelease { versionsToRelease[lang] = { current, next, + dateStamp, }; }); @@ -129,17 +143,10 @@ async function processRelease(): Promise { for (const lang of langsToReleaseOrUpdate) { // prepare the submodule - const clientPath = toAbsolutePath(getLanguageFolder(lang)); - const targetBranch = getTargetBranch(lang); - await run(`git checkout ${targetBranch}`, { cwd: clientPath }); - await run(`git pull origin ${targetBranch}`, { cwd: clientPath }); - console.log(`Generating ${lang} client(s)...`); console.log(await run(`yarn cli generate ${lang}`)); - const dateStamp = new Date().toISOString().split('T')[0]; - const currentVersion = versionsToRelease[lang].current; - const nextVersion = versionsToRelease[lang].next; + const { current, next, dateStamp } = versionsToRelease[lang]; // update changelog const changelogPath = toAbsolutePath( @@ -149,9 +156,7 @@ async function processRelease(): Promise { ? (await fsp.readFile(changelogPath)).toString() : ''; const changelogHeader = willReleaseLibrary(lang) - ? `## [v${nextVersion}](${getGitHubUrl( - lang - )}/compare/v${currentVersion}...v${nextVersion})` + ? `## [v${next}](${getGitHubUrl(lang)}/compare/v${current}...v${next})` : `## ${dateStamp}`; const newChangelog = getMarkdownSection( getMarkdownSection(issueBody, TEXT.changelogHeader), @@ -162,22 +167,7 @@ async function processRelease(): Promise { [changelogHeader, newChangelog, existingContent].join('\n\n') ); - // commit changelog and the generated client - await configureGitHubAuthor(clientPath); - await run(`git add .`, { cwd: clientPath }); - if (willReleaseLibrary(lang)) { - await execa('git', ['commit', '-m', `chore: release ${nextVersion}`], { - cwd: clientPath, - }); - await execa('git', ['tag', `v${nextVersion}`], { cwd: clientPath }); - } else { - await execa('git', ['commit', '-m', `chore: update repo ${dateStamp}`], { - cwd: clientPath, - }); - } - - // add the new reference of the submodule in the monorepo - await run(`git add ${getLanguageFolder(lang)}`); + await run(`git add ${changelogPath}`); } // We push commits from submodules AFTER all the generations are done. @@ -186,14 +176,37 @@ async function processRelease(): Promise { const clientPath = toAbsolutePath(getLanguageFolder(lang)); const targetBranch = getTargetBranch(lang); - await run(`git push origin ${targetBranch}`, { cwd: clientPath }); + const gitHubUrl = getGitHubUrl(lang, { token: process.env.GITHUB_TOKEN }); + const tempGitDir = `${process.env.RUNNER_TEMP}/${lang}`; + await run(`rm -rf ${tempGitDir}`); + await run( + `git clone --depth 1 --branch ${targetBranch} ${gitHubUrl} ${tempGitDir}` + ); + + await run(`cp -r ${clientPath}/ ${tempGitDir}`); + await configureGitHubAuthor(tempGitDir); + await run(`git add .`, { cwd: tempGitDir }); + + const { next, dateStamp } = versionsToRelease[lang]; + if (willReleaseLibrary(lang)) { - await run('git push --tags', { cwd: clientPath }); + await execa('git', ['commit', '-m', `chore: release ${next}`], { + cwd: tempGitDir, + }); + if (process.env.VERSION_TAG_ON_RELEASE === 'true') { + await execa('git', ['tag', `v${next}`], { cwd: tempGitDir }); + await run(`git push --tags`, { cwd: tempGitDir }); + } + } else { + await execa('git', ['commit', '-m', `chore: update repo ${dateStamp}`], { + cwd: tempGitDir, + }); } + await run(`git push`, { cwd: tempGitDir }); } // Commit and push from the monorepo level. - await execa('git', ['commit', '-m', TEXT.commitMessage]); + await execa('git', ['commit', '-m', `chore: release ${getDateStamp()}`]); await run(`git push`); // remove old `released` tag diff --git a/scripts/release/text.ts b/scripts/release/text.ts index 27d74255ba..4a2a2d6b40 100644 --- a/scripts/release/text.ts +++ b/scripts/release/text.ts @@ -23,6 +23,4 @@ export default { `To skip this release, just close the issue.`, `- [ ] ${APPROVED}`, ].join('\n'), - - commitMessage: `chore: update versions and submodules`, };