June 27, 2018
We have recently worked on many chrome extensions. Releasing new chrome extensions manually gets tiring after a while.
So, we thought about automating it with CircleCI, similar to continuous deployment.
We are using the following configuration in circle.yml
to continuously release
chrome extensions from the master branch.
workflows:
version: 2
main:
jobs:
- test:
filters:
branches:
ignore: []
- build:
requires:
- test
filters:
branches:
only: master
- publish:
requires:
- build
filters:
branches:
only: master
version: 2
jobs:
test:
docker:
- image: cibuilds/base:latest
steps:
- checkout
- run:
name: "Install Dependencies"
command: |
apk add --no-cache yarn
yarn
- run:
name: "Run Tests"
command: |
yarn run test
build:
docker:
- image: cibuilds/chrome-extension:latest
steps:
- checkout
- run:
name: "Install Dependencies"
command: |
apk add --no-cache yarn
apk add --no-cache zip
yarn
- run:
name: "Package Extension"
command: |
yarn run build
zip -r build.zip build
- persist_to_workspace:
root: /root/project
paths:
- build.zip
publish:
docker:
- image: cibuilds/chrome-extension:latest
environment:
- APP_ID: <APP_ID>
steps:
- attach_workspace:
at: /root/workspace
- run:
name: "Publish to the Google Chrome Store"
command: |
ACCESS_TOKEN=$(curl "https://accounts.google.com/o/oauth2/token" -d "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token&redirect_uri=urn:ietf:wg:oauth:2.0:oob" | jq -r .access_token)
curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -X PUT -T /root/workspace/build.zip -v "https://www.googleapis.com/upload/chromewebstore/v1.1/items/${APP_ID}"
curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -H "Content-Length: 0" -X POST -v "https://www.googleapis.com/chromewebstore/v1.1/items/${APP_ID}/publish"
We have created three jobs named as test
, build
and publish
and used these
jobs in our workflow to run tests, build the extension, and publish them to the
chrome store, respectively. Every step requires the previous step to run
successfully.
Let's check each job one by one.
test:
docker:
- image: cibuilds/base:latest
steps:
- checkout
- run:
name: "Install Dependencies"
command: |
apk add --no-cache yarn
yarn
- run:
name: "Run Tests"
command: |
yarn run test
We use cibuilds docker image for this job.
First, we do a checkout to the branch and then use yarn
to install
dependencies. Alternatively, we can use npm
to install dependencies as well.
Then, as the last step, we are use yarn run test
to run tests. We can skip
this step if running tests is not needed.
build:
docker:
- image: cibuilds/chrome-extension:latest
steps:
- checkout
- run:
name: "Install Dependencies"
command: |
apk add --no-cache yarn
apk add --no-cache zip
yarn
- run:
name: "Package Extension"
command: |
yarn run build
zip -r build.zip build
- persist_to_workspace:
root: /root/project
paths:
- build.zip
For building chrome extensions, we use the
chrome-extension image. Here, we
also first do a checkout and then, install dependencies using yarn. Note, we are
install zip utility along with yarn because we need to zip our chrome extension
before publishing it in next step. Also, we are not generating version numbers
on our own. The version number will be picked from the manifest file. This step
assumes that we have a task named build
in package.json
to build our app.
The Chrome store rejects multiple uploads with the same version number. So, we have to make sure to update the version number, which should be unique in the manifest file before this step.
In the last step, we use persist_to_workspace
to make build.zip
available
for the next step, publishing.
publish:
docker:
- image: cibuilds/chrome-extension:latest
environment:
- APP_ID: <APP_ID>
steps:
- attach_workspace:
at: /root/workspace
- run:
name: "Publish to the Google Chrome Store"
command: |
ACCESS_TOKEN=$(curl "https://accounts.google.com/o/oauth2/token" -d "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token&redirect_uri=urn:ietf:wg:oauth:2.0:oob" | jq -r .access_token)
curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -X PUT -T /root/workspace/build.zip -v "https://www.googleapis.com/upload/chromewebstore/v1.1/items/${APP_ID}"
curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -H "Content-Length: 0" -X POST -v "https://www.googleapis.com/chromewebstore/v1.1/items/${APP_ID}/publish"
For publishing of the chrome extension, we use the chrome-extension image.
We need APP_ID
, CLIENT_ID
, CLIENT_SECRET
and
REFRESH_TOKEN
/ACCESS_TOKEN
to publish our app to the chrome store.
APP_ID
needs to be fetched from
Google Webstore Developer Dashboard.
APP_ID
is unique for each app whereas CLIENT_ID
, CLIENT_SECRET
and
REFRESH_TOKEN
/ACCESS_TOKEN
can be used for multiple apps. Since APP_ID
is
generally public, we specify that in the yml file. CLIENT_ID
, CLIENT_SECRET
and REFRESH_TOKEN
/ACCESS_TOKEN
are stored as private environment variables
using CircleCI UI. For cases when our app is unlisted in the chrome store, we
need to store APP_ID
as a private environment variable.
CLIENT_ID
and CLIENT_SECRET
need to be fetched from
Google API console. There, we need to
select a project and then click on the credentials tab. If there is no project,
we need to create one and then access the credentials tab.
REFRESH_TOKEN
needs to be fetched from Google API. It also defines the scope
of access for Google APIs. We need to refer to
Google OAuth2
for obtaining the refresh token. We can use any language library.
In the first step of the publish
job, we are attaching a workspace to access
build.zip
, which was created previously. Now, by using all the required tokens
obtained previously, we need to obtain an access token from Google OAuth API
(Link is not available), which must be used to push the app to the chrome store.
Then, we make a PUT
request to the Chrome store API to push the app, and then
use the same API again, to publish the app.
Uploading via API has one more advantage over manual upload. Manual upload generally takes up to 1 hour to show the app in the chrome store. Whereas uploading using Google API generally reflects the app within 5-10 minutes, considering app does not go for a review by Google.
If this blog was helpful, check out our full blog archive.