From 125da9d0ed27b520475293a2b0b5fe7672717476 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 15 Jan 2021 16:27:48 +0100 Subject: [PATCH 1/3] scripts: add check-if-release --- scripts/check-if-release.js | 101 ++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 scripts/check-if-release.js diff --git a/scripts/check-if-release.js b/scripts/check-if-release.js new file mode 100755 index 0000000000..57a0ad792c --- /dev/null +++ b/scripts/check-if-release.js @@ -0,0 +1,101 @@ +#!/usr/bin/env node +/* eslint-disable import/no-extraneous-dependencies */ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This script is used to determine whether a particular commit has changes +// that should lead to a release. It is run as part of the main master build +// to determine whether the release flow should be run as well. +// +// It has the following output which can be used later in GitHub actions: +// +// needs_release = 'true' | 'false' + +const { execFile: execFileCb } = require('child_process'); +const { resolve: resolvePath } = require('path'); +const { promises: fs } = require('fs'); +const { promisify } = require('util'); + +const parentRef = process.env.COMMIT_SHA_BEFORE || 'HEAD^'; + +const execFile = promisify(execFileCb); + +async function runPlain(cmd, ...args) { + try { + const { stdout } = await execFile(cmd, args, { shell: true }); + return stdout.trim(); + } catch (error) { + if (error.stderr) { + process.stderr.write(error.stderr); + } + if (!error.code) { + throw error; + } + throw new Error( + `Command '${[cmd, ...args].join(' ')}' failed with code ${error.code}`, + ); + } +} + +async function main() { + process.cwd(resolvePath(__dirname, '..')); + + const diff = await runPlain( + 'git', + 'diff', + '--name-only', + parentRef, + 'packages/*/package.json', + 'plugins/*/package.json', + ); + const packageList = diff.split(/^(.*)$/gm).filter(s => s.trim()); + + const packageVersions = await Promise.all( + packageList.map(async path => { + const { name, version: newVersion } = JSON.parse( + await fs.readFile(path, 'utf8'), + ); + const { version: oldVersion } = JSON.parse( + await runPlain('git', 'show', `${parentRef}:${path}`), + ); + return { name, oldVersion, newVersion }; + }), + ); + + const newVersions = packageVersions.filter( + ({ oldVersion, newVersion }) => oldVersion !== newVersion, + ); + + if (newVersions.length === 0) { + console.log('No package version bumps detected, no release needed'); + console.log(`::set-output name=needs_release::false`); + return; + } + + console.log('Package version bumps detected, a new release is needed'); + const maxLength = Math.max(...newVersions.map(_ => _.name.length)); + for (const { name, oldVersion, newVersion } of newVersions) { + console.log( + ` ${name.padEnd(maxLength, ' ')} ${oldVersion} -> ${newVersion}`, + ); + } + console.log(`::set-output name=needs_release::true`); +} + +main().catch(error => { + console.error(error.stack); + process.exit(1); +}); From 5fcb150cd475373d11241e1cbde42f191ebd48ea Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 15 Jan 2021 16:28:11 +0100 Subject: [PATCH 2/3] github/workflows: update main master build to use check-if-release script --- .github/workflows/master.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 6f45968002..68b08224ac 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -8,6 +8,9 @@ jobs: build: runs-on: ubuntu-latest + outputs: + needs_release: ${{ steps.release_check.outputs.needs_release }} + strategy: matrix: node-version: [12.x, 14.x] @@ -47,6 +50,15 @@ jobs: run: yarn install --frozen-lockfile # End of yarn setup + - name: Fetch previous commit for release check + run: git fetch origin '${{ github.event.before }}' + + - name: Check if release + id: release_check + run: node scripts/check-if-release.js + env: + COMMIT_SHA_BEFORE: '${{ github.event.before }}' + - name: validate config run: yarn backstage-cli config:check @@ -82,9 +94,10 @@ jobs: # We can't re-use the output from the above step, but we'll have a guaranteed node_modules cache and # only run the build steps that are necessary for publishing release: - if: contains(github.event.commits.*.author.username, 'github-actions[bot]') && contains(github.event.head_commit.message, 'from backstage/changeset-release/master') needs: build + if: needs.build.outputs.needs_release == 'true' + runs-on: ubuntu-latest strategy: From ecd214c4cac66bd33890ab80331d8c72d501ea7f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 17 Jan 2021 19:10:18 +0100 Subject: [PATCH 3/3] scripts/create-github-release: fall back release message to PR contents if not a release PR --- scripts/create-github-release.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/create-github-release.js b/scripts/create-github-release.js index a9bfb9f5b3..1d6a493db3 100755 --- a/scripts/create-github-release.js +++ b/scripts/create-github-release.js @@ -53,7 +53,8 @@ if (!BOOL_CREATE_RELEASE) { const GH_OWNER = 'backstage'; const GH_REPO = 'backstage'; -const EXPECTED_COMMIT_MESSAGE = /^Merge pull request #(?[0-9]+) from backstage\/changeset-release\/master\n\nVersion Packages$/; +const EXPECTED_COMMIT_MESSAGE = /^Merge pull request #(?[0-9]+) from/; +const CHANGESET_RELEASE_BRANCH = 'backstage/changeset-release/master'; // Initialize a GitHub client const octokit = new Octokit({ @@ -116,7 +117,7 @@ async function getCommitMessageUsingTagName(tagName) { } // There is a PR number in our expected commit message. Get the description of that PR. -async function getPrDescriptionFromCommitMessage(commitMessage) { +async function getReleaseDescriptionFromCommitMessage(commitMessage) { // It should exactly match the pattern of changeset commit message, or else will abort. const expectedMessage = RegExp(EXPECTED_COMMIT_MESSAGE); if (!expectedMessage.test(commitMessage)) { @@ -131,20 +132,19 @@ async function getPrDescriptionFromCommitMessage(commitMessage) { `Identified the changeset Pull request - https://github.com/backstage/backstage/pull/${prNumber}`, ); - const prData = await octokit.pulls.get({ + const { data } = await octokit.pulls.get({ owner: GH_OWNER, repo: GH_REPO, pull_number: prNumber, }); - return prData.data.body; -} + // Use the PR description to prepare for the release description + const isChangesetRelease = commitMessage.includes(CHANGESET_RELEASE_BRANCH); + if (isChangesetRelease) { + return data.body.split('\n').slice(3).join('\n'); + } -// Use the PR description to prepare for the release description -async function prepareReleaseDescription(prDescription) { - // TODO: Refine prDescription to remove the lines containing "Update Dependencies" - // Remove everything in the beginning until changelogs. - return prDescription.split('\n').slice(3).join('\n'); + return data.body; } // Create Release on GitHub. @@ -178,8 +178,9 @@ async function createRelease(releaseDescription) { async function main() { const commitMessage = await getCommitMessageUsingTagName(TAG_NAME); - const prDescription = await getPrDescriptionFromCommitMessage(commitMessage); - const releaseDescription = await prepareReleaseDescription(prDescription); + const releaseDescription = await getReleaseDescriptionFromCommitMessage( + commitMessage, + ); await createRelease(releaseDescription); }