Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
138 changes: 89 additions & 49 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand All @@ -79,58 +80,100 @@ 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)

1. Create a [Firefox Add-ons developer account](https://addons.mozilla.org/developers/).
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

Expand Down Expand Up @@ -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
Expand All @@ -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.
Loading
Loading