245 lines
9.3 KiB
YAML
245 lines
9.3 KiB
YAML
name: Build and publish extension
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- "**"
|
|
release:
|
|
types: [published]
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Version to validate (for example 20261009)"
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
build:
|
|
name: Build extension packages
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event.release.tag_name || github.ref }}
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.x"
|
|
|
|
- name: Validate release version
|
|
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
|
|
env:
|
|
RELEASE_VERSION: ${{ github.event.release.tag_name || inputs.version }}
|
|
run: |
|
|
python - <<'PY'
|
|
import json, os, re
|
|
expected = os.environ["RELEASE_VERSION"].removeprefix("v")
|
|
for path in ("manifest.json", "manifest.chrome.json"):
|
|
actual = json.load(open(path, encoding="utf-8"))["version"]
|
|
if actual != expected:
|
|
raise SystemExit(f"{path}: version {actual!r} does not match release {expected!r}")
|
|
if not re.fullmatch(r"\d+(\.\d+){0,3}", expected):
|
|
raise SystemExit(f"Release version must be numeric, got {expected!r}")
|
|
PY
|
|
|
|
- name: Install web-ext
|
|
run: npm install --global web-ext
|
|
|
|
- name: Prepare Firefox source
|
|
run: |
|
|
python - <<'PY'
|
|
from pathlib import Path
|
|
import shutil
|
|
source = Path('.')
|
|
target = Path('dist/firefox-source')
|
|
excluded = {'.git', '.github', 'dist', 'web-ext-artifacts', 'scripts',
|
|
'manifest.chrome.json', 'background-chrome.js',
|
|
'background-service-worker.js', 'browser-api.js'}
|
|
target.mkdir(parents=True, exist_ok=True)
|
|
for item in source.iterdir():
|
|
if item.name in excluded:
|
|
continue
|
|
destination = target / item.name
|
|
if item.is_dir():
|
|
shutil.copytree(item, destination, dirs_exist_ok=True)
|
|
else:
|
|
shutil.copy2(item, destination)
|
|
PY
|
|
|
|
- name: Build Firefox package
|
|
run: |
|
|
web-ext build \
|
|
--source-dir dist/firefox-source \
|
|
--artifacts-dir dist/firefox \
|
|
--filename proxyswitcherforkbywt-firefox.xpi \
|
|
--overwrite-dest
|
|
|
|
- name: Build Chrome package
|
|
run: python scripts/build-chrome.py
|
|
|
|
- name: Upload packages for this push
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: extension-packages-${{ github.sha }}
|
|
if-no-files-found: error
|
|
path: |
|
|
dist/firefox/proxyswitcherforkbywt-firefox.xpi
|
|
web-ext-artifacts/proxyswitcherforkbywt-chrome.zip
|
|
|
|
publish:
|
|
name: Publish stores and attach release assets
|
|
if: github.event_name == 'release' && github.event.action == 'published'
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
env:
|
|
AMO_JWT_ISSUER: ${{ secrets.AMO_JWT_ISSUER }}
|
|
AMO_JWT_SECRET: ${{ secrets.AMO_JWT_SECRET }}
|
|
CWS_PUBLISHER_ID: ${{ secrets.CWS_PUBLISHER_ID }}
|
|
CWS_EXTENSION_ID: ${{ secrets.CWS_EXTENSION_ID }}
|
|
CWS_CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }}
|
|
CWS_CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }}
|
|
CWS_REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }}
|
|
steps:
|
|
- name: Check out release source
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event.release.tag_name }}
|
|
|
|
- name: Check store credentials
|
|
id: credentials
|
|
run: |
|
|
python - <<'PY'
|
|
import os
|
|
stores = {
|
|
'firefox': ('AMO_JWT_ISSUER', 'AMO_JWT_SECRET'),
|
|
'chrome': ('CWS_PUBLISHER_ID', 'CWS_EXTENSION_ID', 'CWS_CLIENT_ID',
|
|
'CWS_CLIENT_SECRET', 'CWS_REFRESH_TOKEN'),
|
|
}
|
|
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
|
|
for store, names in stores.items():
|
|
missing = [name for name in names if not os.environ.get(name, '').strip()]
|
|
output.write(f"{store}={'false' if missing else 'true'}\n")
|
|
if missing:
|
|
print(f"Skipping {store} deployment: missing credentials: {', '.join(missing)}")
|
|
else:
|
|
print(f"{store} deployment enabled")
|
|
PY
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Download build packages
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: extension-packages-${{ github.sha }}
|
|
path: dist/packages
|
|
|
|
- name: Set up Firefox signing tools
|
|
if: steps.credentials.outputs.firefox == 'true'
|
|
run: |
|
|
npm install --global web-ext
|
|
python - <<'PY'
|
|
from pathlib import Path
|
|
import shutil
|
|
source = Path('.')
|
|
target = Path('dist/firefox-source')
|
|
excluded = {'.git', '.github', 'dist', 'web-ext-artifacts', 'scripts',
|
|
'manifest.chrome.json', 'background-chrome.js',
|
|
'background-service-worker.js', 'browser-api.js'}
|
|
target.mkdir(parents=True, exist_ok=True)
|
|
for item in source.iterdir():
|
|
if item.name in excluded:
|
|
continue
|
|
destination = target / item.name
|
|
if item.is_dir():
|
|
shutil.copytree(item, destination, dirs_exist_ok=True)
|
|
else:
|
|
shutil.copy2(item, destination)
|
|
PY
|
|
|
|
- name: Sign and submit Firefox package to AMO
|
|
if: steps.credentials.outputs.firefox == 'true'
|
|
run: |
|
|
test -n "$AMO_JWT_ISSUER" || { echo 'Missing AMO_JWT_ISSUER secret'; exit 1; }
|
|
test -n "$AMO_JWT_SECRET" || { echo 'Missing AMO_JWT_SECRET secret'; exit 1; }
|
|
web-ext sign \
|
|
--source-dir dist/firefox-source \
|
|
--artifacts-dir dist/firefox-signed \
|
|
--api-key "$AMO_JWT_ISSUER" \
|
|
--api-secret "$AMO_JWT_SECRET" \
|
|
--channel listed \
|
|
--approval-timeout 900000 \
|
|
--no-input
|
|
firefox_xpi=$(find dist/firefox-signed -maxdepth 1 -name '*.xpi' -print -quit)
|
|
test -n "$firefox_xpi" || { echo 'AMO did not return a signed XPI'; exit 1; }
|
|
cp "$firefox_xpi" dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
|
|
|
|
- name: Upload Chrome package to Chrome Web Store
|
|
if: steps.credentials.outputs.chrome == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
for name in CWS_PUBLISHER_ID CWS_EXTENSION_ID CWS_CLIENT_ID CWS_CLIENT_SECRET CWS_REFRESH_TOKEN; do
|
|
test -n "${!name}" || { echo "Missing $name secret"; exit 1; }
|
|
done
|
|
token=$(curl --fail --silent --show-error \
|
|
--data-urlencode "client_id=$CWS_CLIENT_ID" \
|
|
--data-urlencode "client_secret=$CWS_CLIENT_SECRET" \
|
|
--data-urlencode "refresh_token=$CWS_REFRESH_TOKEN" \
|
|
--data-urlencode 'grant_type=refresh_token' \
|
|
https://oauth2.googleapis.com/token | python -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')
|
|
chrome_zip=$(find dist/packages -name 'proxyswitcherforkbywt-chrome.zip' -print -quit)
|
|
test -f "$chrome_zip" || { echo "Missing Chrome package: $chrome_zip"; exit 1; }
|
|
curl --fail --silent --show-error \
|
|
-X POST \
|
|
-H "Authorization: Bearer $token" \
|
|
-H 'Content-Type: application/zip' \
|
|
--data-binary "@$chrome_zip" \
|
|
"https://chromewebstore.googleapis.com/upload/v2/publishers/$CWS_PUBLISHER_ID/items/$CWS_EXTENSION_ID:upload"
|
|
curl --fail --silent --show-error \
|
|
-X POST \
|
|
-H "Authorization: Bearer $token" \
|
|
"https://chromewebstore.googleapis.com/v2/publishers/$CWS_PUBLISHER_ID/items/$CWS_EXTENSION_ID:publish"
|
|
|
|
- name: Prepare release assets
|
|
run: |
|
|
mkdir -p dist/release-assets
|
|
chrome_zip=$(find dist/packages -name 'proxyswitcherforkbywt-chrome.zip' -print -quit)
|
|
test -f "$chrome_zip"
|
|
cp "$chrome_zip" dist/release-assets/
|
|
firefox_xpi=dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
|
|
if [ ! -f "$firefox_xpi" ]; then
|
|
firefox_xpi=$(find dist/packages -name 'proxyswitcherforkbywt-firefox.xpi' -print -quit)
|
|
fi
|
|
test -f "$firefox_xpi"
|
|
cp "$firefox_xpi" dist/release-assets/
|
|
|
|
- name: Attach Firefox and Chrome packages to release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
run: |
|
|
gh release upload "$RELEASE_TAG" \
|
|
dist/release-assets/* \
|
|
--clobber
|
|
|
|
- name: Upload release packages as workflow artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: release-extension-packages-${{ github.event.release.tag_name }}
|
|
if-no-files-found: error
|
|
path: dist/release-assets/*
|