DEV Community

Cover image for Automate your chrome extension deployment in minutes!
Gokul Kathirvel
Gokul Kathirvel

Posted on • Edited on • Originally published at gokatz.me

Automate your chrome extension deployment in minutes!

[cover photo by Simon Schmitt on Unsplash ]

NOTE: recently (2nd Sept 2018) rewrote this blog to use 'chrome-webstore-upload' (previously, 'chrome-webstore-upload-cli') to make it more straightforward.

Building a chrome extension is fun! But deployment is a bit tedious as it requires few manual processes. In this post, I’ll Show you how to automate the build, upload and publish process of your chrome extension in a matter of few minutes 🔥🔥

First thing first, thanks to the authors of the following NPM packages which I will be using to automate things.

Let’s get started. One of the perfect places for automating the deployment process is the CI/CD pipelines. I will be demoing using the GitLab’s built-in CI environment with the help of gitlab-ci.yml file. The same can be applied to any CI services for Github like Travis, Circle-CI etc.,

As a chrome extension authors, you may be aware of the following release steps.

  • zipping the extension folder (make sure you bump the manifest version)
  • uploading to chrome web store.
  • publish the uploaded version of the extension.

I'm going to code the automating script using node. So, let's create a node file (deploy.js) in the root directory to invoke it from the CI environment using gitlab-ci.yml file.

zipping the extension folder:

Using the zip-folder package, zipping will be only a command away!

const zipFolder = require('zip-folder');
let folderName = 'path/to/folder'; 
// I too hate placeholders! will be attaching a sample file at the end of this writeup
let zipName = '/path/to/archive.zip';

zipFolder(folderName, zipName, function(err) {
    if(err) {
        console.log('oh no! ', err);
    } else {
        console.log(`Successfully zipped the ${folderName} directory and store as ${zipName}`);
        // will be invoking upload process 
    }
});

Woohoo! You Done zipping your extension 🤟🏻Add this to your deploy.js file.

uploading to chrome web store:

Here comes the another package, chrome-webstore-upload. With the help of this package, we can upload the zipped extension to the chrome web store.

Install the package on your extension project using the command:

yarn add chrome-webstore-upload -D

To upload the file to webstore, this package needs client ID, client secret and refresh token of your extension project. If you are not familiar with those terminologies, don't worry. To use the webstore APIs, Google needs some identifiers and credentials to authorize you and identify your extension.

To get all the three credentials, follow the instruction mentioned here. The package author, Andrew did a great job of adding this guide.

[Take few moments and generate all the three ids and credentials...]

Once you have done with getting all those ids and credentials, store those credentials in your CI environment variable (GitLab) or other CI services' environment variable, like Travis' env variable or circle-ci env variable or anything of your choice. ⚠️ Refrain from checking-in these variables into your codebase as these are your API credentials (like your password!)

Now, we can now start to upload the zip file to webstore upload package. This step has to be invoked on the success of the zipping process.

To use the webstore API, seed them with the created credentials. After seeding the credentials, create a file stream of the zipped extension that needs to be uploaded. Then, call the upload API (uploadExisting) using the created stream.

// getting all the credentials and IDs from `gitlab-ci.yml` file
let REFRESH_TOKEN = process.env.REFRESH_TOKEN; 
let EXTENSION_ID = process.env.EXTENSION_ID;
let CLIENT_SECRET = process.env.CLIENT_SECRET;
let CLIENT_ID = process.env.CLIENT_ID;

const webStore = require('chrome-webstore-upload')({
  extensionId: EXTENSION_ID,
  clientId: CLIENT_ID,
  clientSecret: CLIENT_SECRET,
  refreshToken: REFRESH_TOKEN
});

function upload() {
  const extesnionSource = fs.createReadStream(zipName);
  webStore.uploadExisting(extesnionSource).then(res => {
    console.log('Successfully uploaded the ZIP');    

    // call publish API on success
  }).catch((error) => {
    console.log(`Error while uploading ZIP: ${error}`);
    process.exit(1);
  });
}

The process.env thing is a way to pass the variable from gitlab-ci.yml file to the node process. Will be showing how it works later in this post.

The above method will upload the mentioned zip to the web store as a draft. You can check out the draft at your developer console. Again, make sure you bump the manifest version. Otherwise, the API will fail to upload the zip!

Once we successfully uploaded the zip to webstore, we can publish it using the webstore package's publish API:

Like the upload API, we can invoke the publish API the same way. Make sure you call this API on the success of upload command.


publish() {
  // publish the uploaded zip
  webStore.publish().then(res => {
    console.log('Successfully published the newer version');
  }).catch((error) => {
    console.log(`Error while publishing uploaded extension: ${error}`);
    process.exit(1);
  });
}

You know something? you have done automating the extension deployment process 😍Let's put together all the snippets to give birth to the node process file - deploy.js

As I promised, sample deploy.js file is as follow! Keep it as a reference. Get your hands dirty with code 💻

hi-fi 🙏

Now it’s time to invoke those script from the gitlab-ci.yml file. This is a sample ci file I'm using (to be invoked in GitLab CI environment)

image: node:6.10.3

before_script:
  - yarn

stages:
  - deployExtension

deployExtension:
  stage: deployExtension
  only:
    - master #to be invoked only on master merge
  script:
    - yarn build
    - CLIENT_ID=${CLIENT_ID} CLIENT_SECRET=${CLIENT_SECRET} EXTENSION_ID=${EXTENSION_ID} REFRESH_TOKEN=${REFRESH_TOKEN} node ./deploy.js

the credentials (${CLIENT_ID}, ${CLIENT_SECRET}, ${EXTENSION_ID}, ${REFRESH_TOKEN}) will be pulled from the environemnt variable (in case of GitLab) and passed to the deploy.js file (present in the root directory) as environment variable. Those varables can be accessed using process.env (say, to retrieve CLIENT_ID in node process, use process.env.CLIENT_ID)

PS:

  • You can completely automate this without using a separate node file (deploy.js) with the help of gitlab-ci.yml file alone (using chrome-webstore-upload-cli). But for our convenience and for better error handling, we can go with a separate node file 😉
  • Consider using chalk package to sprinkle some color to the CI/CD logs 🎨

You got a bonus video tutorial 🤩
Joe has done an awesome job taking you to a video tour of this entire process.


I personally love building extensions. But definitely not a PRO! Reach me out if you have any queries with getting started. The package, baby-chrome should help you scaffolding a chrome extension and help get up and running in minutes.

Liquid error: internal

Top comments (7)

Collapse
 
beshur profile image
Alex Buznik

Hi!
Thank you for the tutorial.

As we already using Grunt for building, I used grunt-webstore-upload package which does all the stuff pretty good, no extra code needed.

Anyway, thanks for a great link for the token refresher retrieval - it's awesome.

Cheers!

Collapse
 
gokatz profile image
Gokul Kathirvel • Edited

Thanks for the feedback @beshur :)
Yup. Grunt, a task runner, will not be needed in many cases especially if we opt for a CI/CD setup.

Collapse
 
beshur profile image
Alex Buznik

However we still need to do some clear up and zipping - grunt is good for that, and still needed for Travis.

Collapse
 
dangolant profile image
Daniel Golant

This is excellent! The CRX upload/deploy process is sooo tedious normally. Question though: I have to remove certain fields when I deploy (the key field, which gets sourced from key.pem instead) but for some reason I need to have it locally. Any advice on automatically stripping it from manifest?

Collapse
 
gokatz profile image
Gokul Kathirvel • Edited

Hi Daniel, Sorry that I missed your comment :( I'm working on something similar to this. I need to change some source for the staging environment. Will definitely let you know once I'm done.

Meanwhile, will this package be helpful? npmjs.com/package/chrome-manifest

Collapse
 
jpamorgan profile image
John Philip Morgan

Hi @gokatz ,

thanks for the great tutorial. I was able to get the zip packaging and uploading to work great, but no matter what I try I can't get the publishing to work. When I check for the uploaded file in the webstore dashboard I see it is ready for publishing and I can do it manually just fine. When the POST request runs to publish I get a nondescript error from the endpoint. I have even tried multiple different packages for uploading/publishing the zip.

{"name":"production","nameType":"extension","appError":"WU_FAILED_TO_PUBLISH_EXTENSION","more":{"errors":[{"domain":"global","reason":"badRequest","message":"Publish condition not met: "}],"code":400,"message":"Publish condition not met: "}}
Collapse
 
gokatz profile image
Gokul Kathirvel

Hi John, Glad that you liked the article 🙂

I'm not sure what was the issue in this scenario as the failed condition was not mentioned in the response unlike here. Is the deployment process is open-sourced? so that I can have a look.

Another question, was the first deployment done directly via webstore?