Skip to content

Commit

Permalink
Setup Playground PR preview links
Browse files Browse the repository at this point in the history
  • Loading branch information
carstingaxion committed Aug 30, 2024
1 parent 4782b48 commit c6961a6
Show file tree
Hide file tree
Showing 2 changed files with 321 additions and 0 deletions.
208 changes: 208 additions & 0 deletions .github/scripts/create-preview-links.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* Based on:
* https://github.com/Automattic/themes/blob/a0c9b91f827f46ed60c502d41ef881b2f0552f03/.github/scripts/create-preview-links.js
*/

/*
* This function creates the URL where to download the built & zipped plugin.
*
* @param {object} context - The context of the event that triggered the action.
* @param {number} number - The PR number where the plugin changes are located.
* @returns {string} - The URL where to download the built & zipped plugin, as an artifact of the workflow.
*/
function createBlueprintUrl( context, number ) {
const { repo, owner } = context;
const workflow = encodeURI( 'Build Gutenberg Plugin Zip' );
const artifact = 'gutenberg-plugin-pr';
const proxy = '/plugin-proxy.php';

return `${ proxy }/?org=${ owner }&repo=${ repo }&workflow=${ workflow }&artifact=${ artifact }&pr=${ number }`;
}

/*
* This function creates a WordPress Playground blueprint JSON string for a given PR of the Gutenberg plugin.
*
* @param {object} context - The context of the event that triggered the action.
* @param {number} number - The PR number where the plugin changes are located.
* @param {string} zipArtifactUrl - The URL where to download the built & zipped plugin, as an artifact of the workflow.
* @returns {string} - A JSON string representing the steps of a blueprint.
*/
function createBlueprint( context, number, zipArtifactUrl ) {
const { repo, owner } = context;

// TODO
// Verify that the PR exists and that GitHub CI finished building it
// ...

const template = {
landingPage: '/wp-admin/post-new.php?post_type=post',
preferredVersions: {
php: '8.3',
wp: 'latest',
},
phpExtensionBundles: [ 'kitchen-sink' ],
features: {
networking: true,
},
steps: [
{
step: 'enableMultisite',
},
{
step: 'login',
username: 'admin',
password: 'password',
},
{
step: 'setSiteOptions',
options: {
blogname: `${ owner }/${ repo } PR #${ number }`,
blogdescription: `Testing pull_request #${ number } with Playground`,
},
},
{
step: 'mkdir',
path: '/wordpress/pr',
},
/*
* This is the most important step.
* It download the built plugin zip file exposed by GitHub CI.
*
* Because the zip file is not publicly accessible, we use the
* plugin-proxy API endpoint to download it. The source code of
* that endpoint is available at:
* https://github.com/WordPress/wordpress-playground/blob/trunk/packages/playground/website/public/plugin-proxy.php
*/
{
step: 'writeFile',
path: '/wordpress/pr/pr.zip',
data: {
resource: 'url',
url: zipArtifactUrl,
caption: `Downloading ${ owner }/${ repo } PR #${ number }`,
},
progress: {
weight: 2,
caption: `Applying ${ owner }/${ repo } PR #${ number }`,
},
},
/**
* GitHub CI artifacts are doubly zipped:
*
* pr.zip
* gutenberg-plugin.zip
* gutenberg/
* gutenberg.php
* ... other files ...
*
* This step extracts the inner zip file so that we get
* access directly to gutenberg.zip and can use it to
* install the plugin.
*/
{
step: 'unzip',
zipPath: '/wordpress/pr/pr.zip',
extractToPath: '/wordpress/pr',
},
{
step: 'installPlugin',
pluginZipFile: {
resource: 'vfs',
path: '/wordpress/pr/gutenberg-plugin.zip',
},
},
],
};

return JSON.stringify( template );
}

/*
* This function creates a comment on a PR with preview links.
*
* It is used by `playground-preview` workflow.
*
* @param {object} github - An authenticated instance of the GitHub API.
* @param {object} context - The context of the event that triggered the action.
*/
async function createPreviewLinksComment( github, context ) {
const zipArtifactUrl = createBlueprintUrl(
context.repo,
context.payload.pull_request.number
);
const blueprint = encodeURI(
createBlueprint(
context.repo,
context.payload.pull_request.number,
zipArtifactUrl
)
);
const prText = `for PR#${ context.payload.pull_request.number }`;
const playgrounds = [
{
name: '**Normal** WordPress playground ' + prText,
url: 'https://playground.wordpress.net/#',
},
{
name: '**Seamless** WordPress playground ' + prText,
url: 'https://playground.wordpress.net/?mode=seamless#',
},
{
name: 'WordPress playground **Builder** ' + prText,
url: 'https://playground.wordpress.net/builder/builder.html#',
},
];
const links = playgrounds.map( ( playground ) => ( {
title: playground.name,
url: playground.url + blueprint,
} ) );
const previewLinks = links.map(
( link ) => `- [${ link.title }](${ link.url })\n`
);
const title = '### Preview changes with Playground';
const comment = `
You can preview the least recent changes ${ prText } by following one of the links below:
${ previewLinks.join( ' ' ) }
- [Download <code>.zip</code> with build changes](${ zipArtifactUrl })
**⚠️ Note:** The preview sites are created using [WordPress Playground](https://wordpress.org/playground/). You can add content, edit settings, and test the themes as you would on a real site, but please note that changes are not saved between sessions.
`;

const repoData = {
owner: context.repo.owner,
repo: context.repo.repo,
};

// Check if a comment already exists and update it if it does
const { data: comments } = await github.rest.issues.listComments( {
issue_number: context.payload.pull_request.number,
...repoData,
} );
const existingComment = comments.find(
( comment ) =>
comment.user.login === 'github-actions[bot]' &&
comment.body.startsWith( title )
);
const commentObject = {
body: `${ title }\n${ comment }`,
...repoData,
};

if ( existingComment ) {
// Do not update, but delete and recreate Comment to have a new one after last commit.
await github.rest.issues.deleteComment( {
comment_id: existingComment.id,
...commentObject,
} );
}

// Create a new comment if one doesn't exist
github.rest.issues.createComment( {
issue_number: context.payload.pull_request.number,
...commentObject,
} );
}

module.exports = createPreviewLinksComment;
113 changes: 113 additions & 0 deletions .github/workflows/playground-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# For now (!) the name here is important and should not be changed!
#
# The plugin-proxy.php does a check against exact this workflow name.
name: Playground Preview

# Inspired by, but not based on https://github.com/WordPress/gutenberg/blob/b89fb7b6eaf619bde0269e2a7fbf6245822f6cbf/.github/workflows/build-plugin-zip.yml#L153

on:
# What and Why pull_request_target?
#
# The GitHub Actions built in environment variables and Context
# of pull_request_target event are different from those of pull_request event.
#
# For example, the following environment variables and context are different.
#
# - event_name, GITHUB_EVENT_NAME
# - ref, GITHUB_REF
# - sha, GITHUB_SHA
# - ref_name, GITHUB_REF_NAME
#
# @source https://dev.to/suzukishunsuke/secure-github-actions-by-pullrequesttarget-641#modify-workflows-for-pullrequesttarget

pull_request_target:
types: [opened, synchronize]
paths:
- 'lib/**'
- 'packages/**'
- 'patches/**'
- '**.php'

# Cancels all previous workflow runs for pull requests that have not completed.
concurrency:
# The concurrency group contains the workflow name and the branch name for pull requests
# or the commit hash for any other events.
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request_target' && github.head_ref || github.sha }}
cancel-in-progress: true

jobs:
zip:
name: Build Gutenberg plugin & upload as zipped artifact
runs-on: ubuntu-latest
steps:
# To checkout the merged commit with actions/checkout on pull_request_target event,
# you need to get the pull request by GitHub API
# and set the merge commit hash to actions/checkout input ref.
#
# @source https://dev.to/suzukishunsuke/secure-github-actions-by-pullrequesttarget-641#checkout-merge-commits
- uses: suzuki-shunsuke/get-pr-action@v0.1.0
id: pr

- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ steps.pr.outputs.merge_commit_sha }}
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: latest
coverage: none
tools: wp-cli

- name: Install latest version of dist-archive-command
run: wp package install wp-cli/dist-archive-command:dev-main

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
# Enable built-in functionality for caching and restoring dependencies, which is disabled by default.
# The actions/setup-node uses actions/cache under the hood.
# https://github.com/actions/setup-node#caching-global-packages-data
cache: 'npm'

- name: Npm install
# A "full" install is executed, since `npm ci` does not always exit
# with an error status code if the lock file is inaccurate. This also
# helps to catch dependencies not being installed with exact version.
#
# See: https://github.com/WordPress/gutenberg/issues/16157
# See: https://github.com/WordPress/gutenberg/pull/39865
run: npm install

- name: Build plugin
# - [Incorrect version number used when creating zip archive · Issue #92 · wp-cli/dist-archive-command](https://github.com/wp-cli/dist-archive-command/issues/92)
run: |
npm run build
wp dist-archive . ./${{ github.event.repository.name }}.zip
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ github.event.repository.name }}-pr
path: ./${{ github.event.repository.name }}.zip

comment:
name: Comment with playground link
needs: zip # Ensure this runs after zip job.
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Add Preview Links comment
id: comment-on-pr
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const createPreviewLinks = require('.github/scripts/create-preview-links');
createPreviewLinks(github, context);

0 comments on commit c6961a6

Please sign in to comment.