Skip to content

Commit

Permalink
Merge pull request #995 from lumapps/chore/improve-deploy-chromatic
Browse files Browse the repository at this point in the history
chore(ci): improve chromatic deploy
  • Loading branch information
gcornut committed Jun 19, 2023
2 parents 0242f92 + 045ce17 commit d59808e
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 13 deletions.
13 changes: 3 additions & 10 deletions .github/actions/release-note/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
/* eslint-disable */

const fs = require('fs');
const util = require('util');
const exec = util.promisify(require('child_process').exec);

const { getStoryBookURL } = require('../utils');
const { CHANGELOG_PATH } = require('../../../configs/path');
const VERSION_HEADER_REGEXP = /^## \[(.*?)\]\[\]/mg;
const CHROMATIC_PROJECT_ID = '5fbfb1d508c0520021560f10';

// Get short SHA from version tag (vX.Y.Z)
const getVersionSHA = (versionTag) => exec(`git rev-parse --short ${versionTag}`)
.then(({ stdout }) => stdout.trim());

// Extract changelog from latest version in CHANGELOG.md
async function getLatestVersionChangelog() {
Expand All @@ -29,13 +23,12 @@ async function getLatestVersionChangelog() {
* Generate release note and create a release on GH.
*/
async function main({ versionTag, github, context }) {
const [{ version, versionChangelog }, versionSHA] = await Promise.all([
const [{ version, versionChangelog }, storybookURL] = await Promise.all([
getLatestVersionChangelog(),
getVersionSHA(versionTag),
getStoryBookURL(versionTag),
]);

const changelogURL = `https://github.com/lumapps/design-system/blob/${versionTag}/CHANGELOG.md`;
const storybookURL = `https://${versionSHA}--${CHROMATIC_PROJECT_ID}.chromatic.com/`;

// Release notes
const body = `### [🎨 StoryBook](${storybookURL})\n\n### [📄 Changelog](${changelogURL})\n\n${versionChangelog}`;
Expand Down
47 changes: 47 additions & 0 deletions .github/actions/update-pr-body/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: 'Update PR body'
description: 'Update PR body with pattern replace'

inputs:
pattern:
description: |
RexExp pattern to match.
This can only match on the beginning of a line in the PR body
required: true
replace:
description: |
String to replace the pattern.
Can contain capture groups (`$1` to insert the first capture from the regexp pattern)
required: true
keepLastMatch:
description: |
Set to true to keep only the last match of the pattern and remove the others.
whenNotFound:
description: |
Change behavior when the pattern is not found.
Set to "append" to append the replace string to the end.
Set to "ignore" to skip.
Else an error is thrown.
skipReplaceIf:
description: |
Skip replace if this expression returns true.
This can be used to check a match before replacing it.
Example: `skipReplaceIf: "groups[1] === 'foo'"`
=> Will skip replacing the match if the first capture group is 'foo'.
# Example:
# - name: Update PR Body
# uses: ./.github/actions/update-pr-body-action
# with:
# pattern: 'Storybook: .*'
# replace: 'StoryBook: https://example.com'

runs:
using: composite
steps:
- name: Update PR body
uses: actions/github-script@v6
with:
script: |
require("${{ github.action_path }}")({
github, context, inputs: ${{ toJSON(inputs) }}
})
71 changes: 71 additions & 0 deletions .github/actions/update-pr-body/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Match a pattern in the PR body, replace with a replace pattern.
* If the pattern as multiple matches, all but the last one are removed and the last one is replaced.
*
* Can fall back to appending the replace pattern to the end of the body if the pattern is not found.
* Can skip replace if a condition is met on the match instance.
*/
async function main({ inputs, github, context }) {
const { pattern: patternString, replace, whenNotFound, skipReplaceIf, keepLastMatch } = inputs;
const pattern = new RegExp(`(?<=\n)${patternString}`, 'g');

const { owner, repo } = context.repo;
const pull_number = context.issue.number;

const body = context.payload.pull_request.body;
const matches = Array.from(body.matchAll(pattern));
const lastMatch = matches[matches.length - 1];

let updatedBody = body;
if (!lastMatch) {
console.log(`Pattern ${pattern} does not match`);
if (whenNotFound === 'append') {
console.log(' Appending replace string to the end of the body');
updatedBody += `\n\n${replace}`;
} else if (whenNotFound !== 'ignore') {
throw new Error(` No match found for pattern '${pattern}'`);
}
console.log(' Nothing to do', { whenNotFound });
} else {
console.log(`Pattern ${pattern} matches`);
updatedBody = body.replaceAll(
pattern,
(...groups) => {
// Remove last arg (the body)
groups.pop();
// Get match offset (second to last arg)
const offset = groups.pop();

if (keepLastMatch === 'true' && offset !== lastMatch.index) {
// Not the last occurrence => removing it
return '';
}

const [match, ...captureGroups] = groups;
// Skip if condition met
if (skipReplaceIf && eval(skipReplaceIf)) {
console.log(`Condition "${skipReplaceIf}" met.`);
console.log(` Not replacing the match "${match}"`);
return match;
}
// Replace $1, $2, etc. with captured elements
const replaceText = captureGroups.reduce((a, b, i) => a.replace(`$${i + 1}`, b), replace);
console.log(` Replacing match: "${match}"`);
console.log(` With replace pattern: "${replace}"`);
if (replaceText !== replace)
console.log(` Replace pattern resolved as: "${replaceText}"`);
return replaceText;
},
);
}

// Update PR body
await github.rest.pulls.update({
owner,
repo,
pull_number,
body: updatedBody,
});
}

module.exports = main;
21 changes: 21 additions & 0 deletions .github/actions/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Get short SHA for git ref.
*/
async function getShortSHA(gitRef) {
const util = require('util');
const exec = util.promisify(require('child_process').exec);

return exec(`git rev-parse --short ${gitRef}`)
.then(({ stdout }) => stdout.trim());
}

/**
* Generate StoryBook URL for the given git ref.
*/
async function getStoryBookURL(gitRef) {
const { project_id } = require('../../configs/chromatic');
const shortSHA = await getShortSHA(gitRef);
return `https://${shortSHA}--${project_id}.chromatic.com/`;
}

module.exports = { getStoryBookURL, getShortSHA };
73 changes: 70 additions & 3 deletions .github/workflows/deploy-chromatic-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ name: "Deploy chromatic pull request"

on:
pull_request:
types: [ labeled ]
types: [ labeled, synchronize ]

concurrency:
group: chromatic-${{ github.head_ref }}
cancel-in-progress: true

jobs:
deploy_chromatic:
if: "github.event.label.name == 'need: deploy-chromatic'"
if: "github.event.action == 'labeled' && github.event.label.name == 'need: deploy-chromatic'"
name: "Deploy chromatic"
runs-on: ubuntu-latest
steps:
Expand All @@ -19,18 +19,85 @@ jobs:
with:
fetch-depth: 0 # retrieve all the repo history (required by chromatic)

- name: "Init variables"
id: vars
uses: actions/github-script@v6
with:
script: |
// Link to the current run on github website
const run_html_url = "${{github.event.repository.html_url}}/actions/runs/${{github.run_id}}?pr=${{github.event.pull_request.number}}";
// StoryBook URL
const utils = require('./.github/actions/utils');
const storybook_url = await utils.getStoryBookURL('${{github.event.pull_request.head.sha}}');
return { run_html_url, storybook_url };
- name: "Update PR body: Deploying state"
uses: ./.github/actions/update-pr-body
with:
pattern: 'StoryBook: .*'
replace: 'StoryBook: [Deploying...](${{fromJSON(steps.vars.outputs.result).run_html_url}})'
whenNotFound: 'append'
keepLastMatch: true

- name: "Setup"
uses: ./.github/actions/setup

- name: "Deploy chromatic"
id: deploy
uses: chromaui/action@3f82bf5d065290658af8add6dce946809ee0f923 #v6.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_TOKEN }}
buildScriptName: build:storybook
onlyChanged: true
#skip: true

- if: "failure()"
name: "Update PR body: Failure state"
uses: ./.github/actions/update-pr-body
with:
pattern: 'StoryBook: .*'
replace: 'StoryBook: [:x: Failed](${{fromJSON(steps.vars.outputs.result).run_html_url}})'
whenNotFound: 'ignore'
keepLastMatch: true

- if: "contains(github.event.pull_request.labels.*.name, 'need: deploy-chromatic')"
- if: "always() && contains(github.event.pull_request.labels.*.name, 'need: deploy-chromatic')"
name: "Remove label"
uses: ./.github/actions/remove-label
with:
label: "need: deploy-chromatic"

- name: "Update PR body: Deployed state"
uses: ./.github/actions/update-pr-body
with:
pattern: 'StoryBook: .*'
replace: 'StoryBook: ${{fromJSON(steps.vars.outputs.result).storybook_url}} ([Chromatic build](${{steps.deploy.outputs.buildUrl}}))'
whenNotFound: 'append'
keepLastMatch: true

check_up_to_date:
if: "github.event.action == 'synchronize' && contains(github.event.pull_request.body, '\nStoryBook: https')"
name: "Check StoryBook URL is up-to-date"
runs-on: ubuntu-latest
steps:
- name: "Checkout repository"
uses: actions/checkout@v3

- name: "Get current short SHA"
id: short_sha
run: echo "short_sha=$(git rev-parse --short ${{github.event.pull_request.head.sha}})" >> $GITHUB_OUTPUT

- name: "Mark storybook as outdated"
uses: ./.github/actions/update-pr-body
with:
pattern: 'StoryBook: ((https:\/\/([a-z0-9]+)--[^ ]*).*)'
# Ignore if the short SHA in the storybook URL is up-to-date
skipReplaceIf: |
groups[3] === "${{steps.short_sha.outputs.short_sha}}"
&& !groups[0].includes("Outdated commit")
replace: 'StoryBook: $1 **⚠️ Outdated commit**'
whenNotFound: 'ignore'
keepLastMatch: true

3 changes: 3 additions & 0 deletions configs/chromatic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
project_id: '5fbfb1d508c0520021560f10',
};

0 comments on commit d59808e

Please sign in to comment.