Post 4 of 9 in "Releasing WebExtension using GitHub Actions" series
build-assets-on-release workflow
Let’s create the first workflow that utilizes build-test-pack action and builds release assets for offline distribution once a release has been published.
.github/workflows/build-assets-on-release.yml:
name: Build release assets
on:
release:
# Creating draft releases will not trigger it
types: [published]
jobs:
# We will add 3 jobs here...
The workflow will have 3 jobs:
- ensure-zip: Ensuring we have zip release asset.
- build-signed-crx-asset: Building crx asset.
- build-signed-xpi-asset: Building xpi asset.
ensure-zip job
The first job will find zip asset in the release or build it if not found:
ensure-zip:
runs-on: ubuntu-latest
outputs:
zipAssetId: |
${{ steps.getZipAssetId.outputs.result ||
steps.uploadZipAsset.outputs.id }}
steps:
- uses: actions/checkout@v2
- uses: cardinalby/export-env-action@v1
with:
envFile: './.github/workflows/constants.env'
expand: true
- name: Find out zip asset id from the release
id: getZipAssetId
uses: cardinalby/js-eval-action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSETS_URL: ${{ github.event.release.assets_url }}
ASSET_NAME: ${{ env.ZIP_FILE_NAME }}
with:
expression: |
(await octokit.request("GET " + env.ASSETS_URL)).data
.find(asset => asset.name == env.ASSET_NAME)?.id || ''
- name: Build, test and pack
if: '!steps.getZipAssetId.outputs.result'
id: buildPack
uses: ./.github/workflows/actions/build-test-pack
- name: Upload "extension.zip" asset to the release
id: uploadZipAsset
if: '!steps.getZipAssetId.outputs.result'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ${{ env.ZIP_FILE_PATH }}
asset_name: ${{ env.ZIP_FILE_NAME }}
asset_content_type: application/zip
- At the beginning of each workflow we check out the repo and export env variables from constants.env file.
getZipAssetId
step usesgithub.event.release.assets_url
to get the release assets list and find zip asset id. It may not exist if the workflow was triggered by a release created by a user directly. Usedjs-eval-action
is an action for executing general-purpose JavaScript code.- If it hasn’t been found, we call build-test-pack composite action to build zip asset from the scratch, save it to
env.ZIP_FILE_PATH
and then attach to the release. zipAssetId
job output will have a value from eithergetZipAssetId
step (if zip asset was found in the release) or oruploadZipAsset
step if it has been built and uploaded in the job.
The following 2 jobs will run in parallel using zip asset provided by the job we just created.
This order is defined using needs: ensure-zip
key in the jobs.
build-signed-crx-asset job
crx
file is a distribution package of your extension. When you install an extension from the store, crx
file gets transmitted and installed to your browser. But it can be also installed in offline mode manually or via automation tools. Read more about alternative extension distribution options here.
Actually, crx
file is a kind of zip
file that contains the extension dir. But it also contains some additional metadata required by Chrome browser. Namely, it’s signed using a developer’s private key.
🧱 Prepare
For this step you need to have a pem
private key that is used for offline signing. You can use openssl to generate it.
🔒 Add the key to the repo secrets:
CHROME_CRX_PRIVATE_KEY
- a string containing the private key.
Add the job to the workflow:
build-signed-crx-asset:
needs: ensure-zip
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: cardinalby/export-env-action@v1
with:
envFile: './.github/workflows/constants.env'
expand: true
- name: Download zip release asset
uses: cardinalby/download-release-asset-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
assetId: ${{ needs.ensure-zip.outputs.zipAssetId }}
targetPath: ${{ env.ZIP_FILE_PATH }}
- name: Build offline crx
id: buildOfflineCrx
uses: cardinalby/webext-buildtools-chrome-crx-action@v2
with:
zipFilePath: ${{ env.ZIP_FILE_PATH }}
crxFilePath: ${{ env.OFFLINE_CRX_FILE_PATH }}
privateKey: ${{ secrets.CHROME_CRX_PRIVATE_KEY }}
- name: Upload offline crx release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ${{ env.OFFLINE_CRX_FILE_PATH }}
asset_name: ${{ env.OFFLINE_CRX_FILE_NAME }}
asset_content_type: application/x-chrome-extension
- The job uses
zipAssetId
output fromensure-zip
job to download the asset. webext-buildtools-chrome-crx-action
action uses the private key from secrets to build and sign crx file for offline distribution. This action doesn’t interact with Chrome Web Store.
build-signed-xpi-asset job
Firefox also has options for self-distribution of add-ons. Firefox’s own format of extension package is called xpi
and it also has to be signed. Unlike Chrome’s, this signing procedure is online: we have to ask Firefox server to do it for us.
🧱 Prepare
It’s recommended to create a separate entity for the offline distributed extension on Add-on Developer Hub and use its id for signing xpi files. Otherwise, a new entity will be added for every build. You can find extension UUID in the “Technical Details” section of the created entity.
Next, follow the official documentation and obtain jwtIssuer
and jwtSecret
values required for accessing the API.
🔒 Add these values to repo secrets:
FF_OFFLINE_EXT_ID
- UUID of the add-on entity created for offline distribution.FF_JWT_ISSUER
FF_JWT_SECRET
Finally, add the following job to the workflow:
build-signed-xpi-asset:
needs: ensure-zip
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: cardinalby/export-env-action@v1
with:
envFile: './.github/workflows/constants.env'
expand: true
- name: Download zip release asset
uses: cardinalby/download-release-asset-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
assetId: ${{ needs.ensure-zip.outputs.zipAssetId }}
targetPath: ${{ env.ZIP_FILE_PATH }}
- name: Sign Firefox xpi for offline distribution
id: ffSignXpi
continue-on-error: true
uses: cardinalby/webext-buildtools-firefox-sign-xpi-action@v1
with:
timeoutMs: 1200000
extensionId: ${{ secrets.FF_OFFLINE_EXT_ID }}
zipFilePath: ${{ env.ZIP_FILE_PATH }}
xpiFilePath: ${{ env.XPI_FILE_PATH }}
jwtIssuer: ${{ secrets.FF_JWT_ISSUER }}
jwtSecret: ${{ secrets.FF_JWT_SECRET }}
- name: Abort on sign error
if: |
steps.ffSignXpi.outcome == 'failure' &&
steps.ffSignXpi.outputs.sameVersionAlreadyUploadedError != 'true'
run: exit 1
- name: Upload offline xpi release asset
if: steps.ffSignXpi.outcome == 'success'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ${{ env.XPI_FILE_PATH }}
asset_name: ${{ env.XPI_FILE_NAME }}
asset_content_type: application/x-xpinstall
webext-buildtools-firefox-sign-xpi-action
action uses the keys from secrets to build and sign xpi file for offline distribution. Practice shows that this step can take quite a long time to complete.timeoutMs
input allows you to configure the timeout.continue-on-error: true
key offfSignXpi
step is used to prevent the step to fail immediately in case of error and examine an error at the next step.- At the next step we examine an error (if any) and fail the job, except the case where the error happened because this version was already signed. It’s a peculiarity of Firefox Add-ons signing process: it doesn’t allow to sign the same version twice. So we just suppress the error and don’t fail the entire workflow and just skip the following “upload” step instead.