name: Build and publish extension on: push: branches: - "**" release: types: [published] workflow_dispatch: inputs: version: description: "Version to validate (for example 1.0.1)" 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+){1,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: | mkdir -p dist/firefox-source rsync -a \ --exclude '.git/' \ --exclude '.github/' \ --exclude 'web-ext-artifacts/' \ --exclude 'manifest.chrome.json' \ --exclude 'background-chrome.js' \ --exclude 'background-service-worker.js' \ --exclude 'browser-api.js' \ --exclude 'scripts/' \ ./ dist/firefox-source/ - 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: 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 run: | npm install --global web-ext mkdir -p dist/firefox-source rsync -a \ --exclude '.git/' \ --exclude '.github/' \ --exclude 'web-ext-artifacts/' \ --exclude 'manifest.chrome.json' \ --exclude 'background-chrome.js' \ --exclude 'background-service-worker.js' \ --exclude 'browser-api.js' \ --exclude 'scripts/' \ ./ dist/firefox-source/ - name: Sign and submit Firefox package to AMO 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 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: Attach signed Firefox and Chrome packages to release env: GH_TOKEN: ${{ github.token }} run: | gh release upload "${{ github.event.release.tag_name }}" \ dist/packages/proxyswitcherforkbywt-firefox-signed.xpi \ "$chrome_zip" \ --clobber - name: Upload signed packages as workflow artifacts uses: actions/upload-artifact@v4 with: name: signed-extension-packages-${{ github.event.release.tag_name }} if-no-files-found: error path: | dist/packages/proxyswitcherforkbywt-firefox-signed.xpi dist/packages/**/proxyswitcherforkbywt-chrome.zip