From 993ebf7007f3b227f417eb2064f5c61896bad354 Mon Sep 17 00:00:00 2001 From: Hamza Hamidi <22576950+hamzahamidi@users.noreply.github.com> Date: Sun, 27 Sep 2026 00:14:49 +0200 Subject: [PATCH 1/3] Use Chrome API v2 for release publishing Chrome API v1.1 is nearing its shutdown and the stored refresh token has repeatedly failed. Isolating Chrome publication lets Firefox, Edge, and GitHub Releases complete independently. --- .github/workflows/tagged-release.yml | 52 +++++++++++++++++++++++----- scripts/submit-stores.mjs | 24 +++++++++---- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml index e0d1de2b9..ec424f956 100644 --- a/.github/workflows/tagged-release.yml +++ b/.github/workflows/tagged-release.yml @@ -23,7 +23,6 @@ on: - "v*" permissions: - id-token: "write" contents: "write" env: GH_TOKEN: ${{ github.token }} @@ -94,6 +93,15 @@ jobs: - run: npm run release:firefox-sources + - name: Upload Chrome package artifact + if: github.event_name == 'push' || inputs.submit_stores == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: chrome-extension + path: build/chromium.zip + if-no-files-found: error + retention-days: 1 + - if: github.event_name == 'push' run: | gh release upload ${{github.ref_name}} build/chromium.zip @@ -115,13 +123,7 @@ jobs: fi npm run release:submit -- "${args[@]}" env: - CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} - CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} - CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} - CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} - CHROME_PUBLISH_TARGET: ${{ secrets.CHROME_PUBLISH_TARGET }} - CHROME_DEPLOY_PERCENTAGE: ${{ secrets.CHROME_DEPLOY_PERCENTAGE }} - CHROME_REVIEW_EXEMPTION: ${{ secrets.CHROME_REVIEW_EXEMPTION }} + CHROME_PUBLISH_VIA_ACTION: "true" FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }} FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }} FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }} @@ -134,3 +136,37 @@ jobs: - if: github.event_name == 'push' run: | gh release edit ${{github.ref_name}} --draft=false + + publish_chrome: + if: github.event_name == 'push' || inputs.submit_stores == 'true' + needs: build_and_release + runs-on: ubuntu-latest + environment: chrome-web-store + permissions: + id-token: "write" + concurrency: + group: chrome-web-store + cancel-in-progress: false + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: chrome-extension + - id: auth + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 + with: + workload_identity_provider: ${{ vars.CWS_WIF_PROVIDER }} + service_account: ${{ vars.CWS_SERVICE_ACCOUNT }} + token_format: access_token + access_token_scopes: https://www.googleapis.com/auth/chromewebstore + access_token_lifetime: 1800s + create_credentials_file: false + export_environment_variables: false + - uses: hamzahamidi/publish-to-chrome-web-store@c8919147f8de0d6f1129bec36345e4a9aa638af9 # v1 + with: + access-token: ${{ steps.auth.outputs.access_token }} + publisher-id: ${{ vars.CWS_PUBLISHER_ID }} + item-id: ${{ secrets.CHROME_EXTENSION_ID }} + zip: chromium.zip + deploy-percentage: ${{ secrets.CHROME_DEPLOY_PERCENTAGE }} + skip-review: ${{ secrets.CHROME_REVIEW_EXEMPTION }} + dry-run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == 'true' }} diff --git a/scripts/submit-stores.mjs b/scripts/submit-stores.mjs index 1a64f1722..3552b5f14 100644 --- a/scripts/submit-stores.mjs +++ b/scripts/submit-stores.mjs @@ -32,6 +32,12 @@ const REQUIRED_ENV = [ 'EDGE_CLIENT_ID', 'EDGE_API_KEY', ] +const CHROME_ENV = [ + 'CHROME_EXTENSION_ID', + 'CHROME_CLIENT_ID', + 'CHROME_CLIENT_SECRET', + 'CHROME_REFRESH_TOKEN', +] export function parseArgs(args) { return { @@ -40,7 +46,12 @@ export function parseArgs(args) { } export function findMissingEnv(env = process.env) { - return REQUIRED_ENV.filter((name) => { + const requiredEnv = + env.CHROME_PUBLISH_VIA_ACTION === 'true' + ? REQUIRED_ENV.filter((name) => !CHROME_ENV.includes(name)) + : REQUIRED_ENV + + return requiredEnv.filter((name) => { const value = env[name] return typeof value !== 'string' || value.trim().length === 0 }) @@ -58,11 +69,10 @@ export async function findMissingArtifacts({ exists = fs.pathExists } = {}) { return missing } -export function buildPublishExtensionArgs({ dryRun }) { +export function buildPublishExtensionArgs({ dryRun, skipChrome = false }) { return [ ...(dryRun ? ['--dry-run'] : []), - '--chrome-zip', - 'build/chromium.zip', + ...(!skipChrome ? ['--chrome-zip', 'build/chromium.zip'] : []), '--firefox-zip', 'build/firefox.zip', '--firefox-sources-zip', @@ -199,10 +209,12 @@ export async function submitStores({ argv = process.argv.slice(2), env = process } const manifest = await fs.readJson('build/firefox/manifest.json') - const args = buildPublishExtensionArgs({ dryRun }) + const skipChrome = env.CHROME_PUBLISH_VIA_ACTION === 'true' + const args = buildPublishExtensionArgs({ dryRun, skipChrome }) const firefoxReleaseNotes = buildFirefoxReleaseNotes(manifest.version) + const stores = skipChrome ? 'Firefox and Edge' : 'Chrome, Firefox, and Edge' - console.log(`Submitting ChatGPTBox ${manifest.version} to Chrome, Firefox, and Edge`) + console.log(`Submitting ChatGPTBox ${manifest.version} to ${stores}`) console.log(`Mode: ${dryRun ? 'dry-run' : 'submit'}`) console.log(`Artifacts: ${REQUIRED_ARTIFACTS.join(', ')}`) console.log(`Firefox version notes: ${firefoxReleaseNotes}`) From 5eb4de43c734deec3e4c3f034e18158c5b493760 Mon Sep 17 00:00:00 2001 From: Hamza Hamidi <22576950+hamzahamidi@users.noreply.github.com> Date: Sun, 27 Sep 2026 18:51:02 +0200 Subject: [PATCH 2/3] Update Chrome publishing workflow Queue Chrome uploads and retain their package longer so store review delays do not block release submission. Cover the action-based submission path to protect Firefox and Edge publishing from Chrome credential requirements. --- .github/workflows/tagged-release.yml | 3 ++- tests/unit/release/submit-stores.test.mjs | 27 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml index ec424f956..f7ae4bfb2 100644 --- a/.github/workflows/tagged-release.yml +++ b/.github/workflows/tagged-release.yml @@ -100,7 +100,7 @@ jobs: name: chrome-extension path: build/chromium.zip if-no-files-found: error - retention-days: 1 + retention-days: 30 - if: github.event_name == 'push' run: | @@ -147,6 +147,7 @@ jobs: concurrency: group: chrome-web-store cancel-in-progress: false + queue: max steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: diff --git a/tests/unit/release/submit-stores.test.mjs b/tests/unit/release/submit-stores.test.mjs index dddfcb3d1..db2b25996 100644 --- a/tests/unit/release/submit-stores.test.mjs +++ b/tests/unit/release/submit-stores.test.mjs @@ -68,6 +68,20 @@ test('findMissingEnv accepts required secrets', () => { assert.deepEqual(findMissingEnv(env), []) }) +test('findMissingEnv does not require Chrome credentials when publishing through the action', () => { + const env = { + CHROME_PUBLISH_VIA_ACTION: 'true', + FIREFOX_EXTENSION_ID: 'chatgptbox', + FIREFOX_JWT_ISSUER: 'firefox-issuer', + FIREFOX_JWT_SECRET: 'firefox-secret', + EDGE_PRODUCT_ID: 'edge-product', + EDGE_CLIENT_ID: 'edge-client', + EDGE_API_KEY: 'edge-key', + } + + assert.deepEqual(findMissingEnv(env), []) +}) + test('findMissingEnv treats whitespace-only secrets as missing', () => { const env = { CHROME_EXTENSION_ID: 'chrome-id', @@ -108,6 +122,19 @@ test('buildPublishExtensionArgs includes all stores and dry run', () => { ]) }) +test('buildPublishExtensionArgs skips Chrome when it is published through the action', () => { + const args = buildPublishExtensionArgs({ dryRun: false, skipChrome: true }) + + assert.deepEqual(args, [ + '--firefox-zip', + 'build/firefox.zip', + '--firefox-sources-zip', + 'build/firefox-sources.zip', + '--edge-zip', + 'build/chromium.zip', + ]) +}) + test('buildFirefoxReleaseNotes returns the fixed GitHub release URL', () => { assert.equal( buildFirefoxReleaseNotes('2.6.1'), From dbb17b744e2d2fd5da0ee5dc76481574749fe694 Mon Sep 17 00:00:00 2001 From: Hamza Hamidi <22576950+hamzahamidi@users.noreply.github.com> Date: Thu, 1 Oct 2026 22:37:00 +0200 Subject: [PATCH 3/3] test: cover required Firefox and Edge credentials Keep the combined store submission's Firefox and Edge credentials required when Chrome publishing moves to its action. --- tests/unit/release/submit-stores.test.mjs | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/unit/release/submit-stores.test.mjs b/tests/unit/release/submit-stores.test.mjs index db2b25996..331770ae3 100644 --- a/tests/unit/release/submit-stores.test.mjs +++ b/tests/unit/release/submit-stores.test.mjs @@ -82,6 +82,33 @@ test('findMissingEnv does not require Chrome credentials when publishing through assert.deepEqual(findMissingEnv(env), []) }) +test('findMissingEnv still requires Firefox and Edge credentials when publishing Chrome through the action', () => { + const requiredStoreEnv = [ + 'FIREFOX_EXTENSION_ID', + 'FIREFOX_JWT_ISSUER', + 'FIREFOX_JWT_SECRET', + 'EDGE_PRODUCT_ID', + 'EDGE_CLIENT_ID', + 'EDGE_API_KEY', + ] + const env = { + CHROME_PUBLISH_VIA_ACTION: 'true', + FIREFOX_EXTENSION_ID: 'chatgptbox', + FIREFOX_JWT_ISSUER: 'firefox-issuer', + FIREFOX_JWT_SECRET: 'firefox-secret', + EDGE_PRODUCT_ID: 'edge-product', + EDGE_CLIENT_ID: 'edge-client', + EDGE_API_KEY: 'edge-key', + } + + for (const name of requiredStoreEnv) { + const envWithoutStoreValue = { ...env } + delete envWithoutStoreValue[name] + + assert.deepEqual(findMissingEnv(envWithoutStoreValue), [name]) + } +}) + test('findMissingEnv treats whitespace-only secrets as missing', () => { const env = { CHROME_EXTENSION_ID: 'chrome-id',