Skip to content

Commit

Permalink
GH Actions: Add action to upload release to SVN repo (WordPress#27591)
Browse files Browse the repository at this point in the history
The idea here is to couple the upload of the Gutenberg plugin to the WP.org plugin repo tightly to when a new release is published to GitHub, rather than running it from the local system of whoever's in charge of releasing that version.

Moving parts of the release workflow from locally running scripts to GH actions should overall make the process more easily reproducible, and should free up time and resources required for the release process.

The relevant job needs to be [approved](https://docs.github.com/en/free-pro-team@latest/actions/managing-workflow-runs/reviewing-deployments#approving-or-rejecting-a-job) by a qualified reviewer (i.e. a member of the `gutenberg-core` team). This is to ensure that plugin releases cannot be published by anyone who has commit access to the GitHub repo.
  • Loading branch information
ockham authored and youknowriad committed Jan 6, 2021
1 parent 5308c9d commit f9e1af1
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 251 deletions.
64 changes: 64 additions & 0 deletions .github/workflows/upload-release-to-plugin-repo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
on:
release:
types: [released]

name: Upload Gutenberg plugin to WordPress.org plugin repo

jobs:
upload:
name: Upload Gutenberg Plugin
runs-on: ubuntu-latest
environment: wp.org plugin
if: github.event.release.assets[0]
env:
PLUGIN_REPO_URL: 'https://plugins.svn.wordpress.org/gutenberg'
STABLE_TAG_REGEX: 'Stable tag: [0-9]\+\.[0-9]\+\.[0-9]\+\s*'
SVN_USERNAME: ${{ secrets.svn_username }}
SVN_PASSWORD: ${{ secrets.svn_password }}
VERSION: ${{ github.event.release.name }}
steps:
- name: Check out Gutenberg trunk from WP.org plugin repo
run: svn checkout "$PLUGIN_REPO_URL/trunk"

- name: Get previous stable tag
id: get_previous_stable_tag
run: echo ::set-output name=stable_tag::$(grep "$STABLE_TAG_REGEX" ./trunk/readme.txt)

- name: Delete everything
working-directory: ./trunk
run: find . -maxdepth 1 -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} +

- name: Download and unzip Gutenberg plugin asset into trunk folder
env:
PLUGIN_URL: ${{ github.event.release.assets[0].browser_download_url }}
run: |
curl -L -o gutenberg.zip $PLUGIN_URL
unzip gutenberg.zip -d trunk
rm gutenberg.zip
- name: Replace the stable tag placeholder with the existing stable tag on the SVN repository
env:
STABLE_TAG_PLACEHOLDER: 'Stable tag: V\.V\.V'
STABLE_TAG: ${{ steps.get_previous_stable_tag.outputs.stable_tag }}
run: sed -i "s/${STABLE_TAG_PLACEHOLDER}/${STABLE_TAG}/g" ./trunk/readme.txt

- name: Commit the content changes
working-directory: ./trunk
run: |
svn st | grep '^?' | awk '{print $2}' | xargs -r svn add
svn st | grep '^!' | awk '{print $2}' | xargs -r svn rm
svn commit -m "Committing version $VERSION" \
--no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD"
- name: Create the SVN tag
working-directory: ./trunk
run: |
svn copy "$PLUGIN_REPO_URL/trunk" "$PLUGIN_REPO_URL/tags/$VERSION" -m "Tagging version $VERSION" \
--no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD"
- name: Update the plugin's stable version
working-directory: ./trunk
run: |
sed -i "s/${STABLE_TAG_REGEX}/Stable tag: ${VERSION}/g" ./readme.txt
svn commit -m "Releasing version $VERSION" \
--no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD"
211 changes: 0 additions & 211 deletions bin/plugin/commands/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const fs = require( 'fs' );
const semver = require( 'semver' );
const Octokit = require( '@octokit/rest' );
const { sprintf } = require( 'sprintf-js' );
const os = require( 'os' );

/**
* Internal dependencies
Expand All @@ -19,7 +18,6 @@ const {
runShellScript,
} = require( '../lib/utils' );
const git = require( '../lib/git' );
const svn = require( '../lib/svn' );
const { getNextMajorVersion } = require( '../lib/version' );
const {
getIssuesByMilestone,
Expand All @@ -32,31 +30,6 @@ const {
findReleaseBranchName,
} = require( './common' );

const STABLE_TAG_REGEX = /Stable tag: [0-9]+\.[0-9]+\.[0-9]+\s*\n/;
const STABLE_TAG_PLACEHOLDER = 'Stable tag: V.V.V\n';

/**
* Fetching the SVN repository to the working directory.
*
* @param {string} abortMessage Abort message.
*
* @return {Promise<string>} Repository local path.
*/
async function runSvnRepositoryCheckoutStep( abortMessage ) {
// Cloning the repository
let svnWorkingDirectoryPath;
await runStep( 'Fetching the SVN repository', abortMessage, async () => {
log( '>> Fetching the SVN repository' );
svnWorkingDirectoryPath = svn.checkout( config.svnRepositoryURL );
log(
'>> The SVN repository has been successfully fetched in the following temporary folder: ' +
formats.success( svnWorkingDirectoryPath )
);
} );

return svnWorkingDirectoryPath;
}

/**
* Creates a new release branch based on the last package.json version
* and chooses the next version number.
Expand Down Expand Up @@ -478,149 +451,6 @@ async function runGithubReleaseStep(
return release;
}

/**
* Updates and commits the content of the SVN repo using the new plugin ZIP.
*
* @param {string} svnWorkingDirectoryPath SVN Working Directory Path.
* @param {string} zipPath Plugin zip path.
* @param {string} version Version.
* @param {string} abortMessage Abort Message.
*/
async function runUpdateTrunkContentStep(
svnWorkingDirectoryPath,
zipPath,
version,
abortMessage
) {
// Updating the content of the svn
await runStep( 'Updating trunk content', abortMessage, async () => {
log( '>> Replacing trunk content using the new plugin ZIP' );

const readmePath = svnWorkingDirectoryPath + '/readme.txt';

const previousReadmeFileContent = fs.readFileSync( readmePath, 'utf8' );
const stableTag = previousReadmeFileContent.match(
STABLE_TAG_REGEX
)[ 0 ];

// Delete everything
runShellScript(
'find . -maxdepth 1 -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} +',
svnWorkingDirectoryPath
);

// Update the content using the plugin ZIP
runShellScript( 'unzip ' + zipPath + ' -d ' + svnWorkingDirectoryPath );

// Replace the stable tag placeholder with the existing stable tag on the SVN repository.
const newReadmeFileContent = fs.readFileSync( readmePath, 'utf8' );
fs.writeFileSync(
readmePath,
newReadmeFileContent.replace( STABLE_TAG_PLACEHOLDER, stableTag )
);

let xargsOpts = '';
if ( os.platform === 'linux' ) {
// When xargs receives no arguments, it behaves differently in macOS and linux:
// - macOS: doesn't run
// - linux: run without arguments
//
// In linux, the -r option teaches xargs not to run if it receives no arguments.
xargsOpts = '-r';
}

// Commit the content changes
runShellScript(
"svn st | grep '^?' | awk '{print $2}' | xargs " +
xargsOpts +
' svn add',
svnWorkingDirectoryPath
);
runShellScript(
"svn st | grep '^!' | awk '{print $2}' | xargs " +
xargsOpts +
' svn rm',
svnWorkingDirectoryPath
);
await askForConfirmation(
'Trunk content has been updated, please check the SVN diff. Commit the changes?',
true,
abortMessage
);

svn.commit( svnWorkingDirectoryPath, 'Committing version ' + version );
log( '>> Trunk has been successfully updated' );
} );
}

/**
* Creates a new SVN Tag
*
* @param {string} version Version.
* @param {string} abortMessage Abort Message.
*/
async function runSvnTagStep( version, abortMessage ) {
await runStep( 'Creating the SVN Tag', abortMessage, async () => {
await askForConfirmation(
'Proceed with the creation of the SVN Tag?',
true,
abortMessage
);
svn.tagTrunk(
config.svnRepositoryURL,
version,
'Tagging version ' + version
);
log(
'>> The SVN ' +
formats.success( version ) +
' tag has been successfully created'
);
} );
}

/**
* Updates the stable version of the plugin in the SVN repository.
*
* @param {string} svnWorkingDirectoryPath SVN working directory.
* @param {string} version Version.
* @param {string} abortMessage Abort Message.
*/
async function updateThePluginStableVersion(
svnWorkingDirectoryPath,
version,
abortMessage
) {
// Updating the content of the svn
await runStep(
"Updating the plugin's stable version",
abortMessage,
async () => {
const readmePath = svnWorkingDirectoryPath + '/readme.txt';
const readmeFileContent = fs.readFileSync( readmePath, 'utf8' );
const newReadmeContent = readmeFileContent.replace(
STABLE_TAG_REGEX,
'Stable tag: ' + version + '\n'
);
fs.writeFileSync( readmePath, newReadmeContent );

// Commit the content changes
await askForConfirmation(
'The stable version is updated in the readme.txt file. Commit the changes?',
true,
abortMessage
);

svn.commit(
svnWorkingDirectoryPath,
'Releasing version ' + version
);

log( '>> Stable version updated successfully' );
}
);
}

/**
* Checks whether the milestone associated with the release has open issues or
* pull requests. Returns a promise resolving to true if there are no open issue
Expand Down Expand Up @@ -796,10 +626,6 @@ async function releasePlugin( isRC = true ) {
'Aborting! Make sure to manually cherry-pick the ' +
formats.success( commitHash ) +
' commit to the master branch.';
if ( ! isRC ) {
abortMessage +=
' Make sure to perform the SVN release manually as well.';
}

// Cherry-picking the bump commit into master
await runCherrypickBumpCommitIntoMasterStep(
Expand All @@ -808,43 +634,6 @@ async function releasePlugin( isRC = true ) {
abortMessage
);

if ( ! isRC ) {
abortMessage =
'Aborting! The GitHub release is done. Make sure to perform the SVN release manually.';

await askForConfirmation(
'The GitHub release is complete. Proceed with the SVN release? ',
true,
abortMessage
);

// Fetching the SVN repository
const svnWorkingDirectory = await runSvnRepositoryCheckoutStep(
abortMessage
);
temporaryFolders.push( svnWorkingDirectory );

// Updating the SVN trunk content
await runUpdateTrunkContentStep(
svnWorkingDirectory,
zipPath,
version,
abortMessage
);

abortMessage =
'Aborting! The GitHub release is done, SVN trunk updated. Make sure to create the SVN tag and update the stable version manually.';
await runSvnTagStep( version, abortMessage );

abortMessage =
'Aborting! The GitHub release is done, SVN tagged. Make sure to update the stable version manually.';
await updateThePluginStableVersion(
svnWorkingDirectory,
version,
abortMessage
);
}

abortMessage = 'Aborting! The release is finished though.';
await runCleanLocalFoldersStep( temporaryFolders, abortMessage );

Expand Down
40 changes: 0 additions & 40 deletions bin/plugin/lib/svn.js

This file was deleted.

0 comments on commit f9e1af1

Please sign in to comment.