diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb06c49..2470383 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,17 +64,17 @@ jobs: # "Publishing to the browser stores" section for the one-time setup. # --------------------------------------------------------------------- - # Submits the build to addons.mozilla.org for signing and downloads the - # Mozilla-signed .xpi into dist/, so it gets attached to the GitHub - # release below. The unlisted channel is self-hosted distribution: the - # .xpi installs permanently in regular Firefox without an AMO listing. - # Switch --channel to "listed" to publish on addons.mozilla.org instead. - - name: Sign for Firefox (AMO) + # Submits the build to addons.mozilla.org on the listed channel, which + # publishes it on the public AMO listing after Mozilla's review. Listed + # submissions don't hand back a signed file (AMO serves it from the + # listing), so no .xpi lands in dist/ — the glob below tolerates that. + # The listing itself must already exist; see RELEASING.md. + - name: Publish to Firefox Add-ons (AMO) if: ${{ secrets.AMO_JWT_ISSUER != '' }} env: WEB_EXT_API_KEY: ${{ secrets.AMO_JWT_ISSUER }} WEB_EXT_API_SECRET: ${{ secrets.AMO_JWT_SECRET }} - run: npx --no-install web-ext sign --channel unlisted --artifacts-dir dist + run: npx --no-install web-ext sign --channel listed --artifacts-dir dist - name: Publish GitHub release uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 @@ -83,15 +83,12 @@ jobs: files: | dist/*.zip dist/*.xpi + # dist/*.xpi matches nothing on the listed channel (see above); an + # unmatched glob must warn, not fail the release. + fail_on_unmatched_files: false generate_release_notes: true - # Uploads and publishes the new version to the Chrome Web Store. The - # listing must already exist (the very first submission is manual). - - name: Publish to the Chrome Web Store - if: ${{ secrets.CHROME_REFRESH_TOKEN != '' }} - env: - 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: npx --no-install chrome-webstore-upload --source dist/*.zip + # Chrome is deliberately NOT automated: its API needs a Google Cloud + # OAuth client and a refresh token that Google revokes on its own + # schedule, which is more upkeep than the upload saves. Upload the zip + # from the GitHub Release by hand — see RELEASING.md. diff --git a/CHANGELOG.md b/CHANGELOG.md index a8beebb..cb8b112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). scripts) and declared `minimum_chrome_version` 102. - Development toolchain now targets Node.js 22 (Node 20 reached end of life in April 2026); upgraded `web-ext` to v10. +- Releases now publish to the public addons.mozilla.org listing + (`web-ext sign --channel listed`) instead of producing a self-hosted + unlisted `.xpi`. + +### Removed + +- Dropped the Chrome Web Store upload from the release workflow, along with + the `chrome-webstore-upload-cli` dev dependency. The Web Store API requires + a Google Cloud OAuth client and a refresh token that Google revokes on its + own schedule; maintaining that costs more than the two-minute manual upload + it replaces. `RELEASING.md` documents the manual process. ### Fixed diff --git a/RELEASING.md b/RELEASING.md index 661c848..0e03a82 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -10,9 +10,11 @@ stores, and what to do when something fails. shipping. 2. Go to **Actions → Prepare Release → Run workflow**, pick a bump level (`patch` / `minor` / `major`), and run it. -3. Done. A `vX.Y.Z` tag, GitHub Release, and zip appear automatically; store - publishing also runs if the credentials in - [Store publishing](#store-publishing) are configured. +3. A `vX.Y.Z` tag, GitHub Release, and zip appear automatically, and the + version goes to Firefox Add-ons. +4. If the release should reach Chrome users, upload the zip to the Chrome Web + Store by hand — [Chrome: manual release](#chrome-manual-release), about two + minutes. ## Before you release @@ -55,11 +57,10 @@ Triggered by `v*` tag pushes or by the dispatch above. It: 1. **Verifies the tag matches** the `package.json` and `manifest.json` versions, and fails fast on any mismatch. 2. Re-runs the full check suite and builds the zip (`npm run build`). -3. Signs the build for Firefox and uploads to the Chrome Web Store when the - [store credentials](#store-publishing) are configured; steps without - credentials are skipped. -4. Publishes a GitHub Release with generated notes, the zip, and (when - signing ran) the Mozilla-signed `.xpi`. +3. Submits the build to Firefox Add-ons when the + [AMO credentials](#store-publishing) are configured, and skips the step + when they aren't. Chrome is not automated. +4. Publishes a GitHub Release with generated notes and the zip. ## Manual release (fallback) @@ -79,19 +80,24 @@ match the manifests. ## Store publishing -Publishing is built into the Release workflow but **off by default**: each -store's step runs only when its credentials exist as repository Actions +The two stores are handled differently on purpose: + +- **Firefox is automated.** The Release workflow submits every version to + addons.mozilla.org. It's a single API key pair that doesn't expire. +- **Chrome is manual.** Upload the zip from the GitHub Release yourself; see + [Chrome: manual release](#chrome-manual-release). The Web Store API needs a + Google Cloud OAuth client and a refresh token that Google revokes on its own + schedule, so keeping the automation working costs more attention than the + upload it replaces. It's a two-minute drag-and-drop a few times a year. + +The Firefox step runs only when its credentials exist as repository Actions secrets (**GitHub → Settings → Secrets and variables → Actions**). Without them, releases are GitHub-only. -| Secret | Store | Where to get it | -| ---------------------- | ------- | ------------------------------------------------ | -| `AMO_JWT_ISSUER` | Firefox | addons.mozilla.org → Tools → Manage API Keys | -| `AMO_JWT_SECRET` | Firefox | same page (the "JWT secret") | -| `CHROME_EXTENSION_ID` | Chrome | Developer console, after the first manual upload | -| `CHROME_CLIENT_ID` | Chrome | Web Store API OAuth setup (guide linked below) | -| `CHROME_CLIENT_SECRET` | Chrome | Web Store API OAuth setup | -| `CHROME_REFRESH_TOKEN` | Chrome | Web Store API OAuth setup | +| Secret | Store | Where to get it | +| ---------------- | ------- | -------------------------------------------- | +| `AMO_JWT_ISSUER` | Firefox | addons.mozilla.org → Tools → Manage API Keys | +| `AMO_JWT_SECRET` | Firefox | same page (the "JWT secret") | ### Firefox one-time setup (free) @@ -99,38 +105,75 @@ them, releases are GitHub-only. 2. Generate API credentials at [Manage API Keys](https://addons.mozilla.org/developers/addon/api/key/). 3. Add the `AMO_JWT_ISSUER` and `AMO_JWT_SECRET` secrets. - -From the next release on, the workflow submits the build to Mozilla for -signing and attaches the signed `.xpi` to the GitHub release — installable -permanently in regular Firefox. This uses the **unlisted** (self-hosted) -channel. To publish on addons.mozilla.org instead, create the listing there -once and switch the `web-ext sign` step in `release.yml` to -`--channel listed`. - -### Chrome one-time setup (one-time $5 registration) +4. Create the AMO listing **manually once** — see + [First Firefox Add-ons submission](#first-firefox-add-ons-submission). + +From the next release on, the workflow submits each new version to +addons.mozilla.org on the **listed** channel, which publishes it on the public +AMO listing once Mozilla's review passes (minutes for an automated pass, up to +a few days if a human reviewer picks it up). Listed submissions don't return a +signed file — AMO serves the `.xpi` from the listing — so the GitHub Release +carries the zip only. + +## First Firefox Add-ons submission + +The listed channel updates an existing listing; it does not create one with +metadata. Do the first submission by hand: + +1. Go to + [Submit a New Add-on](https://addons.mozilla.org/developers/addon/submit/), + choose **On this site** (listed), and upload the + `copy-pr-link-vX.Y.Z.zip` from any release. +2. Fill in name, summary, description, and category (Developer Tools) — the + README's Usage section is the source for the description, same copy as the + Chrome listing. +3. Submit for review. Once the listing exists, every later release goes up + automatically. + +Mozilla requires the source code for reviewers when a submission contains +built or minified code. This extension ships plain unbundled JavaScript, so +answer **no** to the "Do you use tools to generate this code?" question. + +## Chrome: manual release + +Chrome publishing is not automated — see the reasoning in +[Store publishing](#store-publishing). Do this after each release whose +changes are worth shipping to Chrome users; skipping a version is fine, the +store only cares that the next upload's version is higher than the published +one. + +### Every release (about 2 minutes) + +1. Download `copy-pr-link-vX.Y.Z.zip` from the + [latest GitHub Release](https://github.com/ShiosOS/copy-pr-link/releases/latest). +2. Open the item in the + [developer console](https://chrome.google.com/webstore/devconsole/) → + **Package → Upload new package**, and pick the zip. +3. If the changes affect what the listing claims, update the description or + screenshots on **Store listing**. +4. **Submit for review.** Review takes anywhere from a few hours to a few + days; you get an email either way. + +The console warns about `browser_specific_settings` and `background.scripts` +in the manifest on every upload. That's the Firefox half of the cross-browser +config — Chrome ignores both keys and the upload is accepted. + +### One-time setup ($5 registration) 1. Register as a [Chrome Web Store developer](https://chrome.google.com/webstore/devconsole/) ($5, one time). -2. Create the listing **manually once** — see - [First Chrome Web Store submission](#first-chrome-web-store-submission) - below. Note the extension ID the console assigns. -3. Follow the - [Chrome Web Store API guide](https://developer.chrome.com/docs/webstore/using-api) - to create OAuth credentials (client ID, client secret, refresh token). -4. Add the four `CHROME_*` secrets. - -From the next release on, the workflow uploads the new version and submits it -for review automatically. The uploaded version must be higher than the -published one, which the release flow guarantees. +2. On the account **Settings** page, add and verify the publisher contact + email. Publishing is blocked until the verification link is clicked, so + start with this. +3. Create the listing — see + [First Chrome Web Store submission](#first-chrome-web-store-submission). ## First Chrome Web Store submission -The very first listing is manual. Upload the `copy-pr-link-vX.Y.Z.zip` from -any release, then work through the console tabs. The console will warn about -`browser_specific_settings` and `background.scripts` in the manifest — that's -the Firefox half of the cross-browser config; Chrome ignores it and the -upload is accepted. +Upload the `copy-pr-link-vX.Y.Z.zip` from any release, then work through the +console tabs below. Later uploads reuse all of this — only the package +changes. ### Privacy practices tab @@ -176,11 +219,6 @@ checkboxes. - **Description:** summarize the README's Usage section; minimum 25 characters. -### Settings page (account-level, one time) - -Add and verify the publisher contact email — publishing is blocked until the -email-verification link is clicked, so start this first. - ## Troubleshooting - **Prepare Release fails with "nothing to release"** — the changelog's @@ -192,8 +230,10 @@ email-verification link is clicked, so start this first. already submitted to AMO (e.g. a re-run of a previous release). Cut a new version instead of re-running. - **Re-running a Release for an existing tag** re-creates the GitHub Release - assets safely, but store steps may fail as above; prefer a new patch + assets safely, but the AMO step may fail as above; prefer a new patch release over re-runs. +- **The Chrome console rejects the upload as "version already exists"** — that + version is already published. Upload the next release instead. - **The release commit shows no CI run on `main`** — expected: pushes made with the workflow's `GITHUB_TOKEN` don't trigger workflows. The identical tree already passed the full check inside Prepare Release. diff --git a/package-lock.json b/package-lock.json index 7b1c459..2977a05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@types/chrome": "^0.2.5", "@types/node": "^26.1.2", "@vitest/coverage-v8": "^4.1.10", - "chrome-webstore-upload-cli": "^4.0.1", "eslint": "^10.8.0", "eslint-config-prettier": "^10.1.8", "globals": "^17.9.0", @@ -945,9 +944,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -965,9 +961,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -985,9 +978,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1005,9 +995,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1025,9 +1012,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1045,9 +1029,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2396,16 +2377,6 @@ "concat-map": "0.0.1" } }, - "node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2559,43 +2530,6 @@ "node": ">=12.13.0" } }, - "node_modules/chrome-webstore-upload": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/chrome-webstore-upload/-/chrome-webstore-upload-6.0.0.tgz", - "integrity": "sha512-wUPi8iwR2KPPPytHY8rstA5tbcLp2r8fMYnqMUIgci1q1A6QFYiyeiyG/aqAj35PzNuCTpZseD1VZ9y1b/Fkmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "junk": "^4.0.1", - "yazl": "^3.3.1" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/fregante" - } - }, - "node_modules/chrome-webstore-upload-cli": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chrome-webstore-upload-cli/-/chrome-webstore-upload-cli-4.0.1.tgz", - "integrity": "sha512-UGmvbEGTqNCD+W9HDEoYnZwrbp++v5WcIyVVZWxK7AxFMfxtZF/yiNtxWCcAetNg4z0m/CNhf+Qt3b0aitDEWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chrome-webstore-upload": "^6.0.0", - "meow": "^12.1.1" - }, - "bin": { - "chrome-webstore-upload": "source/cli.js" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/fregante" - } - }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -4392,19 +4326,6 @@ "setimmediate": "^1.0.5" } }, - "node_modules/junk": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", - "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -4645,9 +4566,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4669,9 +4587,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4693,9 +4608,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4717,9 +4629,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4916,19 +4825,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -6832,16 +6728,6 @@ "node": ">=12" } }, - "node_modules/yazl": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-3.3.1.tgz", - "integrity": "sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "^1.0.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 9cdb107..e363a24 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "@types/chrome": "^0.2.5", "@types/node": "^26.1.2", "@vitest/coverage-v8": "^4.1.10", - "chrome-webstore-upload-cli": "^4.0.1", "eslint": "^10.8.0", "eslint-config-prettier": "^10.1.8", "globals": "^17.9.0",