From 6f42eee43af55a8174e0b526cf76c8f1fd55e384 Mon Sep 17 00:00:00 2001 From: limerc Date: Thu, 17 Sep 2026 13:25:59 +0200 Subject: [PATCH 1/2] ci: migrate GitHub Actions to Node.js 24 Bump checkout, setup-node and upload-artifact to v7 and action-gh-release to v3. Replace the browser-actions publishers, which have no Node 24 release, with web-ext sign and the Chrome Web Store API. --- .github/workflows/build.yml | 6 +-- .github/workflows/release.yml | 88 +++++++++++++++++++++++++++-------- 2 files changed, 71 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0558903..01fe499 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,10 +12,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: '24' cache: 'npm' @@ -36,7 +36,7 @@ jobs: run: npm run build - name: Upload extension artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: extension-build path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a40d626..0003269 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: '24' cache: 'npm' @@ -119,7 +119,7 @@ jobs: # GitHub Release # ========================================================================= - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: generate_release_notes: true files: | @@ -155,13 +155,11 @@ jobs: # Firefox Add-ons Publishing # ========================================================================= - name: Publish to Firefox Add-ons - uses: browser-actions/release-firefox-addon@v0.2.1 - with: - addon-id: ${{ secrets.FIREFOX_ADDON_ID }} - addon-path: light-session-${{ steps.version.outputs.VERSION }}-firefox.zip - source-path: light-session-${{ steps.version.outputs.VERSION }}-source.zip - license: MIT - approval-note: | + env: + VERSION: ${{ steps.version.outputs.VERSION }} + WEB_EXT_API_KEY: ${{ secrets.FIREFOX_API_ISSUER }} + WEB_EXT_API_SECRET: ${{ secrets.FIREFOX_API_SECRET }} + APPROVAL_NOTES: | Source archive for this exact version is attached. Build environment: @@ -176,18 +174,68 @@ jobs: cd extension && zip -r ../light-session-VERSION-firefox.zip manifest.json dist/ popup/ icons/ -x "*.map" See AMO-SOURCE-README.md in the source archive for details. - auth-api-issuer: ${{ secrets.FIREFOX_API_ISSUER }} - auth-api-secret: ${{ secrets.FIREFOX_API_SECRET }} - release-note: "See release notes at https://github.com/11me/light-session/releases/tag/v${{ steps.version.outputs.VERSION }}" + run: | + # Sign from the exact files in the Firefox zip (extension/ now holds the Chrome build) + mkdir -p amo-upload + unzip -q "light-session-${VERSION}-firefox.zip" -d amo-upload + + jq -n \ + --arg notes "$APPROVAL_NOTES" \ + --arg release "See release notes at https://github.com/11me/light-session/releases/tag/v${VERSION}" \ + '{version: {license: "MIT", approval_notes: $notes, release_notes: {"en-US": $release}}}' \ + > amo-metadata.json + + # Add-on ID comes from browser_specific_settings.gecko.id in the manifest + npx web-ext sign \ + --source-dir amo-upload \ + --channel listed \ + --amo-metadata amo-metadata.json \ + --upload-source-code "light-session-${VERSION}-source.zip" \ + --approval-timeout 0 # ========================================================================= # Chrome Web Store Publishing # ========================================================================= - name: Publish to Chrome Web Store - uses: browser-actions/release-chrome-extension@v0.2.1 - with: - extension-id: ${{ secrets.CHROME_EXTENSION_ID }} - extension-path: light-session-${{ steps.version.outputs.VERSION }}-chrome.zip - oauth-client-id: ${{ secrets.CHROME_CLIENT_ID }} - oauth-client-secret: ${{ secrets.CHROME_CLIENT_SECRET }} - oauth-refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }} + env: + VERSION: ${{ steps.version.outputs.VERSION }} + EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + run: | + set -euo pipefail + API=https://www.googleapis.com + + ACCESS_TOKEN=$(curl -sSf https://oauth2.googleapis.com/token \ + -d client_id="$CLIENT_ID" \ + -d client_secret="$CLIENT_SECRET" \ + -d refresh_token="$REFRESH_TOKEN" \ + -d grant_type=refresh_token | jq -r .access_token) + echo "::add-mask::$ACCESS_TOKEN" + AUTH=(-H "Authorization: Bearer $ACCESS_TOKEN" -H "x-goog-api-version: 2") + + UPLOAD=$(curl -sS "${AUTH[@]}" -X PUT \ + -T "light-session-${VERSION}-chrome.zip" \ + "$API/upload/chromewebstore/v1.1/items/$EXTENSION_ID") + echo "$UPLOAD" + STATE=$(jq -r .uploadState <<< "$UPLOAD") + + for _ in $(seq 1 30); do + [ "$STATE" != "IN_PROGRESS" ] && break + sleep 10 + STATE=$(curl -sS "${AUTH[@]}" \ + "$API/chromewebstore/v1.1/items/$EXTENSION_ID?projection=DRAFT" | jq -r .uploadState) + done + if [ "$STATE" != "SUCCESS" ]; then + echo "::error::Chrome Web Store upload failed: $STATE" + exit 1 + fi + + PUBLISH=$(curl -sS "${AUTH[@]}" -X POST -H "Content-Length: 0" \ + "$API/chromewebstore/v1.1/items/$EXTENSION_ID/publish") + echo "$PUBLISH" + if ! jq -e 'any(.status[]?; . == "OK" or . == "ITEM_PENDING_REVIEW")' <<< "$PUBLISH" > /dev/null; then + echo "::error::Chrome Web Store publish failed" + exit 1 + fi From c46995f1c0cef2298ecb751fe0160c2fb6bffc67 Mon Sep 17 00:00:00 2001 From: limerc Date: Thu, 17 Sep 2026 13:41:10 +0200 Subject: [PATCH 2/2] ci: use Chrome Web Store API v2 and skip existing AMO versions API v1.1 is shut down on 2026-10-15. Also surface OAuth and upload error bodies, and make the Firefox step safe to re-run. --- .github/workflows/release.yml | 64 ++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0003269..10098da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -175,6 +175,28 @@ jobs: See AMO-SOURCE-README.md in the source archive for details. run: | + set -euo pipefail + + # Skip if this version was already submitted (e.g. re-running a failed job) + JWT=$(node -e ' + const c = require("crypto"); + const b64 = (o) => Buffer.from(JSON.stringify(o)).toString("base64url"); + const now = Math.floor(Date.now() / 1000); + const data = b64({ alg: "HS256", typ: "JWT" }) + "." + + b64({ iss: process.env.WEB_EXT_API_KEY, jti: c.randomUUID(), iat: now, exp: now + 60 }); + const sig = c.createHmac("sha256", process.env.WEB_EXT_API_SECRET).update(data).digest("base64url"); + console.log(data + "." + sig); + ') + echo "::add-mask::$JWT" + ADDON_ID=$(jq -er .browser_specific_settings.gecko.id extension/manifest.firefox.json) + STATUS=$(curl -sS -o /dev/null -w '%{http_code}' -H "Authorization: JWT $JWT" \ + "https://addons.mozilla.org/api/v5/addons/addon/${ADDON_ID}/versions/v${VERSION}/") + case "$STATUS" in + 200) echo "Version ${VERSION} already exists on AMO, skipping"; exit 0 ;; + 404) ;; + *) echo "::error::AMO version lookup failed: HTTP $STATUS"; exit 1 ;; + esac + # Sign from the exact files in the Firefox zip (extension/ now holds the Chrome build) mkdir -p amo-upload unzip -q "light-session-${VERSION}-firefox.zip" -d amo-upload @@ -194,48 +216,52 @@ jobs: --approval-timeout 0 # ========================================================================= - # Chrome Web Store Publishing + # Chrome Web Store Publishing (API v2) # ========================================================================= - name: Publish to Chrome Web Store env: VERSION: ${{ steps.version.outputs.VERSION }} + PUBLISHER_ID: ${{ secrets.CHROME_PUBLISHER_ID }} EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} run: | set -euo pipefail - API=https://www.googleapis.com + ITEM="publishers/${PUBLISHER_ID}/items/${EXTENSION_ID}" + API=https://chromewebstore.googleapis.com - ACCESS_TOKEN=$(curl -sSf https://oauth2.googleapis.com/token \ + ACCESS_TOKEN=$(curl -sS --fail-with-body https://oauth2.googleapis.com/token \ -d client_id="$CLIENT_ID" \ -d client_secret="$CLIENT_SECRET" \ -d refresh_token="$REFRESH_TOKEN" \ - -d grant_type=refresh_token | jq -r .access_token) + -d grant_type=refresh_token | jq -er .access_token) echo "::add-mask::$ACCESS_TOKEN" - AUTH=(-H "Authorization: Bearer $ACCESS_TOKEN" -H "x-goog-api-version: 2") + AUTH=(-H "Authorization: Bearer $ACCESS_TOKEN") - UPLOAD=$(curl -sS "${AUTH[@]}" -X PUT \ + RESPONSE=$(curl -sS --fail-with-body "${AUTH[@]}" -X POST \ -T "light-session-${VERSION}-chrome.zip" \ - "$API/upload/chromewebstore/v1.1/items/$EXTENSION_ID") - echo "$UPLOAD" - STATE=$(jq -r .uploadState <<< "$UPLOAD") + "$API/upload/v2/${ITEM}:upload") + echo "$RESPONSE" + STATE=$(jq -r .uploadState <<< "$RESPONSE") for _ in $(seq 1 30); do [ "$STATE" != "IN_PROGRESS" ] && break sleep 10 - STATE=$(curl -sS "${AUTH[@]}" \ - "$API/chromewebstore/v1.1/items/$EXTENSION_ID?projection=DRAFT" | jq -r .uploadState) + RESPONSE=$(curl -sS --fail-with-body "${AUTH[@]}" "$API/v2/${ITEM}:fetchStatus") + echo "$RESPONSE" + STATE=$(jq -r .lastAsyncUploadState <<< "$RESPONSE") done - if [ "$STATE" != "SUCCESS" ]; then + if [ "$STATE" != "SUCCEEDED" ]; then echo "::error::Chrome Web Store upload failed: $STATE" exit 1 fi - PUBLISH=$(curl -sS "${AUTH[@]}" -X POST -H "Content-Length: 0" \ - "$API/chromewebstore/v1.1/items/$EXTENSION_ID/publish") - echo "$PUBLISH" - if ! jq -e 'any(.status[]?; . == "OK" or . == "ITEM_PENDING_REVIEW")' <<< "$PUBLISH" > /dev/null; then - echo "::error::Chrome Web Store publish failed" - exit 1 - fi + RESPONSE=$(curl -sS --fail-with-body "${AUTH[@]}" -X POST \ + -H "Content-Type: application/json" -d '{}' \ + "$API/v2/${ITEM}:publish") + echo "$RESPONSE" + case "$(jq -r .state <<< "$RESPONSE")" in + PENDING_REVIEW|STAGED|PUBLISHED|PUBLISHED_TO_TESTERS) ;; + *) echo "::error::Chrome Web Store publish failed"; exit 1 ;; + esac