support for firefox - claude did 2 days of work in 15m?#16
Merged
Conversation
Member
|
Oh amazing! Give me a chance to test the functionality and we can merge and release no biggie! 🍕 |
The original commit got Firefox loading end-to-end (polyfill,
gecko block, postbuild rewrite). This pass shores up everything
around it so the second target is a first-class citizen, not a
side experiment.
Code / build:
- Extract the Firefox manifest rewrite into a pure helper at
`scripts/firefox-manifest.mjs` (in-place mutation that takes
the Chromium-shaped manifest and emits the Firefox shape).
`firefox-postbuild.mjs` is now a thin file-IO wrapper around it.
Postbuild also throws loudly if `background.service_worker` is
missing — a silent no-op there would ship a broken Firefox build.
- `src/manifest.ts`: rewrite the comments to explain WHY each
branch field exists (gecko id, strict_min_version=121.0,
data_collection_permissions=['none'], minimum_chrome_version=116)
and why we cap manifest description at 132 chars (CWS + AMO).
- `scripts/zip-extension.mjs`:
· Branch the "Next steps" output by target — Chromium gets
"Open chrome://extensions / Load unpacked", Firefox gets
"Open about:debugging / Load Temporary Add-on" + the temporary
install caveat + the Developer-Edition-with-signatures-off path.
· Verify-manifest-at-root error message now mentions both stores
and both sideload flows, and points at the right rebuild
command for the failing target.
- `.github/workflows/release.yml`: build + zip BOTH targets and
attach both to the draft GitHub release. CI now runs the same
`npm run zip` / `npm run zip:firefox` scripts as locally so the
manifest-at-root verification + sourcemap stripping run in CI.
Tests (+9, total now 176):
- `tests/scripts/firefox-manifest.test.ts` (5 tests): rewrite
swaps service_worker → scripts (with type=module preserved);
throws when there's no service_worker to rewrite; strips
use_dynamic_url from every WAR entry; no-ops on missing WAR;
mutates in place and returns the same reference.
- `tests/manifest.test.ts` (4 tests): default build adds
minimum_chrome_version + omits gecko; TARGET=firefox adds the
full gecko block + omits minimum_chrome_version (mutual
exclusion); shared MV3 fields (name, version, permissions,
host_permissions, icons, description) stay identical between
targets; description respects the 132-char store ceiling.
Docs:
- `README.md`: full Firefox install/update section parallel to
the existing Chromium one — both Option A (temporary add-on,
any FF 121+, resets on restart) and Option B (persistent on
Developer Edition / Nightly with signatures off + .zip → .xpi
rename). Troubleshooting table gains the Firefox "extension
appears to be corrupt" row. Scripts table covers the Firefox
build/zip/release-dry commands. Releasing section explains the
dual-target CI flow. Architecture comment notes manifest.ts's
TARGET branching.
- `PRIVACY.md`: WebExtension storage terminology now spans
Chromium + Firefox (chrome.storage / browser.storage),
Screenshot permission justification mentions both APIs, remote-
code section mentions both build commands.
- Top-of-file comments in `firefox-manifest.mjs`,
`firefox-postbuild.mjs`, and `zip-extension.mjs` document
every rewrite, every fallback path, and the temporary-vs-
persistent install matrix.
Verified: validate (typecheck/lint/format/176 tests) green; both
`npm run release:dry` and `npm run release:dry:firefox` produce
zips with the right manifest shape (verified service_worker vs
scripts, gecko presence, minimum_chrome_version mutual exclusion,
no use_dynamic_url leftovers in the Firefox build).
Co-authored-by: Cursor <cursoragent@cursor.com>
Composes validate (once) + Chromium build + Chromium zip + Firefox build + Firefox zip. Mirrors the full release workflow exactly, which was previously only reachable by running both per-target release:dry scripts back-to-back (and revalidating each time, ~7s of duplicated typecheck/lint/format/tests). This is now the recommended local smoke-test before tagging a release. The single-target scripts stay around for the case where you only care about one browser family. README: scripts table gains the new entry; Releasing section recommends `release:dry:both` first, with the per-target scripts as the narrower-scope alternatives. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds first-class Firefox 121+ support alongside the existing Chromium build. Same source, dual build pipeline, dual zip in every release.
What's in the branch
Code
webextension-polyfill(runtime): everychrome.*import →import browser from 'webextension-polyfill'. Promise-based on both browsers, behavior identical on Chromium.src/manifest.ts: branches onprocess.env.TARGET === 'firefox'. Firefox build addsbrowser_specific_settings.gecko(id,strict_min_version: 121.0,data_collection_permissions: { required: ['none'] }); Chromium build keepsminimum_chrome_version: 116. Manifest description capped at 132 chars (CWS + AMO ceiling) with a build-time guard.scripts/firefox-manifest.mjs(new, pure helper): rewritesbackground.service_worker→background.scripts(array,type: 'module'preserved) and strips Chromium-onlyuse_dynamic_urlfromweb_accessible_resources. Throws if there's noservice_workerto rewrite — silent no-op there would ship a broken Firefox build.scripts/firefox-postbuild.mjs: now a thin file-IO wrapper around the helper. Runs aftervite buildwhenTARGET=firefox.scripts/zip-extension.mjs: target-aware "Next steps" output — Chromium getschrome://extensions/ Load unpacked, Firefox getsabout:debugging/ Load Temporary Add-on plus the temporary-install caveat and the Developer-Edition-with-signatures-off path. Verify-manifest-at-root error message now mentions both stores + both rebuild commands.vite.config.ts:outDirswitches todist-firefox/whenTARGET=firefox.eslint.config.js/.gitignore/.prettierignore: pick updist-firefox/alongsidedist/.CI
.github/workflows/release.ymlnow builds + packages BOTH targets and attaches both zips to the draft GitHub release. Uses the samenpm run zip[:firefox]scripts as locally so the manifest-at-root verification + sourcemap stripping run in CI exactly as they do on a dev machine.Tests (+9, total 176)
tests/scripts/firefox-manifest.test.ts(5): rewrite swapsservice_worker→scriptswithtype: 'module'preserved; throws when there's noservice_workerto rewrite; stripsuse_dynamic_urlfrom every WAR entry; no-ops on missing WAR; mutates in place + returns same reference.tests/manifest.test.ts(4): default build addsminimum_chrome_version+ omits gecko;TARGET=firefoxadds full gecko block + omitsminimum_chrome_version(mutual exclusion); shared MV3 fields stay identical between targets; description respects the 132-char store ceiling.Docs
README.md: full Firefox install/update section parallel to the Chromium one — Option A (temporary add-on, any FF 121+, resets on restart) and Option B (persistent on Developer Edition / Nightly:xpinstall.signatures.required = false+.zip→.xpirename). Troubleshooting table gains the Firefox "extension appears to be corrupt" row. Scripts table coversbuild:firefox/zip:firefox/release:dry:firefox. Releasing section explains the dual-target CI flow. Architecture comment notesmanifest.ts'sTARGETbranching.PRIVACY.md: storage area names use the cross-browser form (storage.sync/storage.local); permission justifications mention bothchrome.tabs.captureVisibleTabandbrowser.tabs.captureVisibleTab; remote-code section calls out both build commands.Local commands
Verification
npm run validate→ typecheck / lint / format / 176 tests green.npm run release:dry+npm run release:dry:firefox→ both succeed.background.service_worker,minimum_chrome_version: 116, no gecko block.background.scripts: [...](type: 'module'preserved), nominimum_chrome_version, full gecko block withdata_collection_permissions: { required: ['none'] }, zerouse_dynamic_urlleftovers in WAR.Install for testers
Firefox 121+ (any channel)
clay-slip-vX.Y.Z-firefox.zipfrom the next release and unzip.about:debugging#/runtime/this-firefox→ Load Temporary Add-on… → pick the unzippedmanifest.json.(Resets on browser restart. For persistent install, see README → Install → Firefox → Option B.)
Chromium (Chrome / Edge / Brave / Arc / Vivaldi / Opera)
Unchanged from before —
clay-slip-vX.Y.Z.zip+chrome://extensions→ Load unpacked.