Host Storybook for each pull request with CircleCI and GitHub Deployments
Kasper Mikiewicz
Posted on May 23, 2019
Originally posted at kasper.io
Lately at company, we had a lot of work with developing UI components using React. It went smooth thanks to Storybook. We hosted files generated by Storybook for each push to branch which was really helpful for quality assurance process.
Storybook is a tool for developing UI components in isolation. While this tool is useful for local development, it's also possible to build a static version of Storybook and host it. I'll show how to configure a deploy for each push made to repository.
You will learn how to build Storybook on CircleCI and use it as a hosting. You will also learn how to use GitHub Deployments. Deployments are requests to deploy a specific branch, commit, tag. External services can listen for those requests and act.
This guide assumes that you have initialized Storybook using @storybook/cli
. If not, go here to learn how to do it.
TL;DR: Here is a repository with whole process configured. List of deployments can be viewed here and deployment assigned to pull request can be viewed here.
Whole process looks like this:
- Make a push to repository
- CircleCI build is triggered
- GitHub Deployment is created
- Install dependencies
- Build storybook
- Save generated files as CircleCI artifacts
- If whole process was successful, add success deployment status
- If whole process was not successful, add error deployment status
- We can see link to generated files on deployments page
- We can see link to generated files in related pull request
Setting up CircleCI
Go to CircleCI Dashboard and add your project. Start the build process - it will fail at first but we will fix it in next steps.
Create CircleCI config file
In your git repository, create .circleci/config.yml
:
version: 2.1
jobs:
build-storybook:
working_directory: ~/repo
docker:
- image: circleci/node:lts
steps:
- checkout
- run:
name: Create GitHub Deployment
command: ./tasks/deployment/start.sh > deployment
- restore_cache:
keys:
- cache-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- cache-
- run:
name: Installing Dependencies
command: npm install
- run:
name: Build Storybook
command: npm run build-storybook
- store_artifacts:
path: storybook-static
- run:
name: Add GitHub Deployment success status
command: ./tasks/deployment/end.sh success
when: on_success
- run:
name: Add GitHub Deployment error status
command: ./tasks/deployment/end.sh error
when: on_fail
- save_cache:
paths:
- node_modules
key: cache-{{ checksum "package.json" }}
workflows:
deploy:
jobs:
- build-storybook
There are 3 parts that are related to creating and adding status updates of GitHub Deployments. This command will create a Deployment and save it's id to deployment
file. Deployment will be visible in related pull request as pending.
- run:
name: Create GitHub Deployment
command: ./tasks/deployment/start.sh > deployment
Only one of other two commands will execute. Execution is based on the status of whole build.
- run:
name: Add GitHub Deployment success status
command: ./tasks/deployment/end.sh success
when: on_success
- run:
name: Add GitHub Deployment error status
command: ./tasks/deployment/end.sh error
when: on_fail
Create deployment scripts
Now create 2 files:
tasks/deployment/start.sh
- this will create a GitHub Deployment.
#!/bin/sh
set -eu
token=${GITHUB_DEPLOYMENTS_TOKEN:?"Missing GITHUB_TOKEN environment variable"}
if ! deployment=$(curl -s \
-X POST \
-H "Authorization: bearer ${token}" \
-d "{ \"ref\": \"${CIRCLE_SHA1}\", \"environment\": \"storybook\", \"description\": \"Storybook\", \"transient_environment\": true, \"auto_merge\": false, \"required_contexts\": []}" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/deployments"); then
echo "POSTing deployment status failed, exiting (not failing build)" 1>&2
exit 1
fi
if ! deployment_id=$(echo "${deployment}" | python -c 'import sys, json; print json.load(sys.stdin)["id"]'); then
echo "Could not extract deployment ID from API response" 1>&2
exit 3
fi
echo ${deployment_id} > deployment
tasks/deployment/end.sh
- this will update Deployment status to success or error.
#!/bin/sh
set -eu
token=${GITHUB_DEPLOYMENTS_TOKEN:?"Missing GITHUB_TOKEN environment variable"}
if ! deployment_id=$(cat deployment); then
echo "Deployment ID was not found" 1>&2
exit 3
fi
if [ "$1" = "error" ]; then
curl -s \
-X POST \
-H "Authorization: bearer ${token}" \
-d "{\"state\": \"error\", \"environment\": \"storybook\"" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/deployments/${deployment_id}/statuses"
exit 1
fi
if ! repository=$(curl -s \
-X GET \
-H "Authorization: bearer ${token}" \
-d "{}" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}"); then
echo "Could not fetch repository data" 1>&2
exit 1
fi
if ! repository_id=$(echo "${repository}" | python -c 'import sys, json; print json.load(sys.stdin)["id"]'); then
echo "Could not extract repository ID from API response" 1>&2
exit 3
fi
path_to_repo=$(echo "$CIRCLE_WORKING_DIRECTORY" | sed -e "s:~:$HOME:g")
url="https://${CIRCLE_BUILD_NUM}-${repository_id}-gh.circle-artifacts.com/0${path_to_repo}/storybook-static/index.html"
if ! deployment=$(curl -s \
-X POST \
-H "Authorization: bearer ${token}" \
-d "{\"state\": \"success\", \"environment\": \"storybook\", \"environment_url\": \"${url}\", \"target_url\": \"${url}\", \"log_url\": \"${url}\"}" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/deployments/${deployment_id}/statuses"); then
echo "POSTing deployment status failed, exiting (not failing build)" 1>&2
exit 1
fi
It might be necessary to update scripts file mode to executable:
git update-index --add --chmod=+x ./tasks/deployment/start.sh
git update-index --add --chmod=+x ./tasks/deployment/end.sh
Configure GitHub access token
Go to https://github.com/settings/tokens and create a new access token. Required scopes:
repo:status
repo_deployment
-
public_repo
Copy new token and go to Environment Variables
configuration section in CircleCI project. If you can't find it, use this url, just replace GITHUB_USERNAME and REPOSITORY_NAME with valid values:
https://circleci.com/gh/GITHUB_USERNAME/REPOSITORY_NAME/edit#env-vars
On CircleCI add variable:
name: GITHUB_DEPLOYMENTS_TOKEN
value: xxxx-xxxx-xxxx-your-github-token
Result
Now whenever you push a new commits to your repository, you will get a storybook hosted on CircleCI. The link to storybook will be added to repository deployments page and to the related pull request.
Bonus
Are you working in company? Create a company github bot account and use it's personal access token to deploy. Customize it's name and avatar.
Posted on May 23, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.