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

11/3/2020 | Michael "XFox" Gordeev

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

So, what CI/CD exactly is?

Contiuous integration and delivery allows 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?

The answer is easy: all you need is to set up your CI/CD pipeline. But actually, it't not that simple if you try to do it yourslef 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 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.

  1. Navigate to you 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. Path to the file cannot though

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

  2. 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:

    1. We don't want to update our extension package if we just edited some typos in repo documentation
    2. We don't want our changes from development brach be published as a major update
    3. Everytime 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

  3. 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

Here ends general preparation of the workflow and now we will move to store-spesific 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.

  1. 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!

  2. 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

    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 derrived from official source) and publishes it to the webstore

    Here 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 thorugh 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
  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)

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: https://xfox111.net/vtnlk8

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 a 'OAuth2' topic

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:

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 ❤ this, you can Buy Me a Coffee ☕ or follow me on Twitter 🗨. Thanks for your time ;)

Cheers,
XFox 🦊

// Tags: Article, Browser Extensions, CD, CI, Devnotes, Firefox, GitHub, Google Chrome

Comments: 0

// Add comment on Blogspot