Software Developement
July 31, 2021

Setting up CI/CD for Web-browser extension with GitHub Actions

Bottom text
Reupload of my posts from Blogspot from 3-Nov-20

Hi there!

Last time I've told you how to debug web extensions with VS Code. This time I going to show you some tools which help you to automate your extension development workflow. I'm talking about continuous integration and delivery

So, what CI/CD exactly is?

Continuous integration and delivery allow you to automate some parts of a development workflow like building, testing, packaging and distributing. So, everything you need here is to push your code into your repository and the rest will be done by computer

Cool, how can I opt-in?

Of course! All you need is to set up your CI/CD pipeline. But actually, it's not that simple if you try to do it yourself since there're a lot of underwater rocks. So that is why I'm doing this article

Today I'll cover set-ups of CI/CD pipelines for Chrome and Firefox extension webstores

Prerequisites

So, all you need right now is your WebExtension source code saved in GitHub repo. I will use for reference my latest and greatest Password generator extension. You can also check it out later to see how it is done there

Note that your project has to have at least one submission to selected stores to be automated

Workflow file setup

So, first thing we need to do is create our workflow file. These files contain instructions for computer on what and how it should integrate and deliver your extension. Usually, WFs have a .yaml file extension. So, GitHub Actions WF is not an exclusion

Navigate to your repository root folder and create a .yaml file on /.github/workflows/your_workflow_file_name.yaml. Name of the file can be anything you want

So, here I've created a file on the exact folder path (you shouldn't push your file for now though)

[Insert image here]

Next thing, we need to fill our file with base instructions which we will use no matter where we will publish our project

name: CI	# Name of your workflow

on:
  workflow_dispatch:	# Allows you to manually trigger workflow
  push:
    # Triggers workflow only if code is pushed to master/main branch
    branches: [ master, main ]
    paths:
      # Triggers workflow only if manifest content is changed
      - 'manifest.json'

jobs:

Here we've set some triggers: conditions in which pipeline will be started

Here our triggers are set to fire only if we changed extension manifest on master/main branch. We did it because of several reasons:

  • Here our triggers are set to fire only if we changed extension manifest on master/main branch. We did it because of several reasons:
  • We don't want our changes from development branch be published as a major update
  • Every time we submit new extension version, we have to change its version in manifest, so every push to master/main we want to publish has to contain this change
Note that YAML file syntax is indetation-sensitive. Make sure you didn't mess with tabs and spaces. More information about workflow file syntax you can find here

Now we need to make sure we have GitHub Actions turned on for our repository

To do that we need to navigate to Settings -> Actions of the repo (https://github.com/%username%/%repository%/settings/actions) and make sure we have checked anything but Disable Actions

[Insert image here]

Here ends general preparation of the workflow and now we will move to store-specific actions

Setting up Firefox webstore submission

Obtain API credential

Since we deal with some sensitive data here, we need to make sure everything is secured and nobody can sabotage our release process. So we need to obtain some access keys which will authorize our pipelines.

First thing, we need to go to Firefox Add-in API management page, generate and copy JWT issuer value (that will be our client ID value) and JWT secret (client secret)

Note: do not share these values with anyone else! Especially, DO NOT put these values into your workflow file as a plain text!

[Put Image here]

Navigate to the repository's secrets page (Settings -> Secrets or https://github.com/%username%/%repository%/settings/secrets) and create two new secrets:

  • FIREFOX_API_KEY - Paste here your JWT issuer token
  • FIREFOX_CLIENT_SECRET - Put your JWT secret token here

[Put Image here]

You can use any other names for your secrets' variables

This is a safe storage for sensitive data. No one will be able to values stored here. Even you won't be able to see them

Update the workflow file

Now we need to add a new job to our workflow which will push our extension to the webstore

Open your workflow YAML file and add a new job:

jobs:
  Firefox:	# Job name
    runs-on: ubuntu-latest	# specify required OS for deployment machine

    steps:
    - uses: actions/checkout@v2	# Download your source code to the DM

    - name: Build Extension for Firefox
      id: web-ext-build
      uses: kewisch/action-web-ext@v1
      with:
        cmd: build

    - name: 'Sign & publish'
      id: web-ext-sign
      uses: kewisch/action-web-ext@v1
      with:
        cmd: sign
        channel: listed
        source: ${{ steps.web-ext-build.outputs.target }}
        apiKey: ${{ secrets.FIREFOX_API_KEY }}
        apiSecret: ${{ secrets.FIREFOX_CLIENT_SECRET }}

    - name: Drop artifacts
      uses: actions/upload-artifact@v2
      with:
        name: 'Firefox Artefacts'
        path: ${{ steps.web-ext-sign.outputs.target }}

Here we have 4 tasks in our job:

  1. actions/checkout@v2 clones our source code to a deployment machine
  2. kewisch/action-web-ext@v1 with cmd: build packs our extension source code into XPI file which will be published to the webstore
  3. kewisch/action-web-ext@v1 with cmd: sign signs the package (confirms that this package is legit and derived from official source) and publishes it to the webstore
  4. actions/upload-artifact@v2 drops our XPI file to workflow artifacts and makes it available for us to download for sideloading and testing purposes (optional task)

In third sigining task we have to provide some parameters to publish our extension correctly:

  • cmd: sign: The command to run. This command signs and submits our extension to Firefox servers for verification
  • source: path to our XPI package file. Here we use ${{ steps.web-ext-build.outputs.target }} since it is an environmental variable
  • channel: Once verification is complete, our extension will become available for everybody in the webstore (listed) or it will become available for self-distribution and won't become visible through the webstore (unlisted). Leave it channel: listed
  • apiKey: our JWT issuer token required for legit publishing. We can use here our secret variables - ${{ secrets.VARIABLE_NAME }}. In our case it's ${{ secrets.FIREFOX_API_KEY }}
  • apiSecret: JWT secret token

More info on workflow variables can be found on official GitHub documentation. If you click on links from actions list above, you can find out more about specific action and its documentation

Setting up Google Chrome webstore submission

Obtain API credential

For Chrome webstore a process of obtaining credential is a bit tricky and requires a lot of explanations. So, instead of making another article I just leave a link to a guideline which covers the case

All we need here is to obtain client ID, client secret and refresh token. For more information what is it and how to live with it you cand find by googling an 'OAuth2' topic

[Put Image Here]

Update the workflow file

Same as setting up Firefox workflow, we now need to add a new job to our pipeline:

jobs:
  Chrome:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Pack extension
      uses: TheDoctor0/zip-release@0.4.1
      with:
      	filename: ./Package.zip
      	exclusions: '.git/* .vscode/* .github/* *.md'

    - name: Publish to Chrome Webstore
      uses: SebastienGllmt/chrome-addon@v3
      with:
      	extension: yourextensionidhere
      	zip: ./Package.zip
      	client-id: ${{ secrets.CHROME_CLIENT_ID }}
      	client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
      	refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}

    - name: Drop artifacts
      uses: actions/upload-artifact@v2
      with:
      	name: 'Chrome Artifacts'
      	path: ./Package.zip

As you can see, there're some major differences between Firefox and Chrome jobs. Here's some key points:

  • We don't have to pack our extension with special format. Everything we need is to create a zip archive of our file with manifest in the root
  • We don't have to sign our package. Everything will be done after submission
  • We need to specify extension ID and provide a refresh token since we will publish our extension using "3d party" (technically, our) application

Everything else is pretty much the same as it is on the Firefox job, so I see no reason to stay on it anymore

More information about used actions you can find their marketplace pages:


So, here's our complete workflow file:

So, that's it. All we have to do now is to push our updated workflow file to the repo and trigger our workflow and make sure everything is up and running

Wait, and how do I set up CI/CD for Safari/Microsoft Edge/etc. extensions?

You can't. Unfortunately, neither Apple nor Microsoft didn't make action tasks for automatic deployment of their browsers' extensions. And while it's understandable for the former one, since Safari has a different architecture of extensions and different submission processes, it's unclear for Microsoft not doing this, since new MS Edge is basically modified Google Chrome and it has same architecture and same submission processes as Chrome

Other browsers either aren't just popular enough or are based on different architecture

Conclusion

Hope this article will help you with your own project. If you still have any questions left, feel free to ask them in the comment section below. You can also leave a topic suggestion for my next article in the comment section as well

If you like ❤️ this, you can Buy Me a Coffee ☕ or follow me on Twitter 🗨. Thanks for your time ;)

Cheers,
XFox 🦊