diff --git a/.gitignore b/.gitignore index 294efab..954f4e4 100644 --- a/.gitignore +++ b/.gitignore @@ -32,14 +32,10 @@ node_modules # Unpacked build output. Regenerated by "make", never edited by hand. build -# Intermediate packages. -logsdu-*.zip - -# Packaged .xpi files are NOT ignored. They are the published artifact: the -# signed add-on is what people download and install, so it belongs in the -# repository (or attached to a release) rather than being rebuilt by everyone -# who wants to install it. An unsigned local build can be removed with -# "make clean". +# dist/ holds the publishable packages and is NOT ignored: the signed add-on +# is what people download and install, so it belongs in the repository (or +# attached to a release) rather than being rebuilt by everyone who wants it. +# Local unsigned builds land there too; "make clean" removes the directory. # Exclude sourcemaps *.map diff --git a/Makefile b/Makefile index caf1f77..4479078 100644 --- a/Makefile +++ b/Makefile @@ -19,33 +19,41 @@ # * rcb@beco.cc * # ************************************************************************** -# Makefile for logsdu - build the unpacked extension into build/. +# Makefile for logsdu - build the extension for Firefox and Chrome. # # Usage: -# make # typecheck and bundle into build/ +# make # typecheck and bundle for Firefox into build/ +# make chrome # same, but with Chrome's manifest, into build/ # make test # run the unit tests -# make xpi # build, then package build/ as logsdu-.xpi -# make clean # remove build/ and the package +# make smoke # check the background bundle works as a service worker +# make xpi # package the Firefox build -> dist/logsdu-.xpi +# make crx # package the Chrome build -> dist/logsdu--chrome.zip +# make packages # both of the above +# make clean # remove build/ and dist/ +# +# build/ holds the unpacked extension and is regenerated from scratch; dist/ +# holds the packages meant to be published, and is kept out of build/ so that +# packaging never tries to include its own output. # # Dependencies are installed with pnpm, never npm: # corepack pnpm install # -# Permanent install (Firefox ESR, Developer Edition or Nightly): -# set xpinstall.signatures.required=false in about:config, then -# about:addons -> gear -> Install Add-on From File -> pick the .xpi +# Load the unpacked build while developing: +# Firefox about:debugging -> This Firefox -> Load Temporary Add-on -> +# build/manifest.json (dropped when Firefox restarts) +# Chrome chrome://extensions -> Developer mode -> Load unpacked -> build/ # -# Release Firefox refuses unsigned add-ons whatever that pref says. There the -# same .xpi has to go through addons.mozilla.org as an unlisted add-on first, -# which signs it automatically without publishing or reviewing it. -# -# Throwaway install for development: about:debugging -> This Firefox -> -# Load Temporary Add-on -> pick build/manifest.json (dropped on restart). +# Publishing: +# Firefox upload dist/*.xpi at addons.mozilla.org +# Chrome upload dist/*-chrome.zip at chrome.google.com/webstore/devconsole EXT_ID := logsdu VERSION := $(shell node -p "require('./package.json').version") -XPI := $(EXT_ID)-$(VERSION).xpi +DIST := dist +XPI := $(DIST)/$(EXT_ID)-$(VERSION).xpi +CRX := $(DIST)/$(EXT_ID)-$(VERSION)-chrome.zip -.PHONY: all build chrome test xpi clean check-deps +.PHONY: all build chrome test smoke xpi crx packages clean check-deps all: build @@ -55,23 +63,29 @@ all: build test: node --test "src/**/*.test.ts" +# Runs the built background bundle in a service-worker-shaped sandbox, which +# is where a Chrome-only breakage would otherwise hide until runtime. +smoke: build + node tools/sw-smoke.mjs + # Call the local toolchain directly, so this works regardless of how pnpm is # provided (corepack vs standalone). Run "corepack pnpm install" first. build: check-deps node_modules/.bin/tsc -noEmit -skipLibCheck node esbuild.config.mjs production -# The same sources with Chrome's background key. Untested against Chrome; it -# exists so the port is a build flag rather than a fork. +# The same sources with Chrome's manifest. Firefox and Chrome disagree on the +# background key and on the gecko block, so the manifest is generated per +# target rather than forked. chrome: check-deps node_modules/.bin/tsc -noEmit -skipLibCheck TARGET=chrome node esbuild.config.mjs production - @echo "Chrome build in build/ -- load it via chrome://extensions (Developer mode)." + @echo "Chrome build in build/ -- chrome://extensions -> Developer mode -> Load unpacked." # An .xpi is just a zip of the extension directory, with the manifest at the -# top level rather than inside a wrapper folder. The same file installs -# directly on ESR and uploads to AMO for signing. +# top level rather than inside a wrapper folder. xpi: build + @mkdir -p $(DIST) rm -f $(XPI) cd build && zip -qr ../$(XPI) . @echo @@ -80,7 +94,8 @@ xpi: build @echo "This file is UNSIGNED. Two ways to use it:" @echo @echo " Publish -- upload it at addons.mozilla.org/developers/addon/submit/" - @echo " Mozilla signs it; the signed file installs on any Firefox." + @echo " Listed add-ons are signed once review approves them; unlisted" + @echo " ones are signed straight away." @echo @echo " Install locally -- only on ESR, Developer Edition or Nightly:" @echo " 1. about:config -> xpinstall.signatures.required = false" @@ -89,9 +104,22 @@ xpi: build @echo " $(CURDIR)/$(XPI)" @echo +# The Chrome Web Store takes a plain zip, and does the packing into .crx itself. +crx: chrome + @mkdir -p $(DIST) + rm -f $(CRX) + cd build && zip -qr ../$(CRX) . + @echo + @echo "Built: $(CURDIR)/$(CRX)" + @echo "Upload it at chrome.google.com/webstore/devconsole" + @echo + +# Both packages. The Firefox build runs last so build/ is left in the state +# the development instructions assume. +packages: crx xpi + clean: - rm -rf build - rm -f $(EXT_ID)-*.xpi $(EXT_ID)-*.zip + rm -rf build $(DIST) # Fail with a useful message rather than a confusing "tsc: not found". check-deps: diff --git a/README.md b/README.md index 3f8c549..15da879 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Once it is published, from addons.mozilla.org. Until then, build it yourself: ``` corepack pnpm install # first time, or after a dependency change -make xpi # typecheck, bundle, and package logsdu-.xpi +make xpi # typecheck, bundle, and package dist/logsdu-.xpi ``` `make xpi` prints the full path of the file and how to install it. The package @@ -118,23 +118,56 @@ check. ## Chrome -`make chrome` builds it. The only difference is the background key: Firefox MV3 -uses an event page, Chrome MV3 requires a service worker, so the manifest is -generated per target rather than duplicated. Everything else -- `chrome.*` -namespace, MV3, no Firefox-only APIs -- is already shared. +``` +make chrome # unpacked Chrome build in build/ +make crx # package it as dist/logsdu--chrome.zip +``` -It has not been tested against Chrome. Do not assume it works. +Load `build/` via `chrome://extensions` -> Developer mode -> **Load unpacked**, +or upload the zip at +[the Web Store dashboard](https://chrome.google.com/webstore/devconsole). + +One codebase, two manifests. Chrome MV3 requires a background *service worker* +and rejects Firefox's event-page `background.scripts`; Firefox needs the gecko +block that Chrome has no use for. `esbuild.config.mjs` writes the right manifest +per target, so the port is a build flag rather than a fork. Everything else -- +the `chrome.*` namespace, MV3, the permission model -- is shared. + +Two things that differ in practice, both handled: + +- **Icons must be raster.** Chrome does not accept SVG in `icons`, so the PNGs + in `icons/` are generated from `logsdu.svg` and both browsers use those. +- **Service workers have no `window` or `document`.** A stray reference through + a shared import would break Chrome only, silently, at runtime. `make smoke` + runs the built background bundle in a worker-shaped sandbox to catch that. + +What has been verified: Chrome 151 loads the build without errors, and the +background bundle registers exactly one content script for exactly the +configured origin. What has **not** been verified is a real login against a live +portal in Chrome. + +A caveat that applies to both browsers: the page-world filler is injected as a +`