diff --git a/Makefile b/Makefile index 4479078..8a8b030 100644 --- a/Makefile +++ b/Makefile @@ -22,16 +22,19 @@ # Makefile for logsdu - build the extension for Firefox and Chrome. # # Usage: -# make # typecheck and bundle for Firefox into build/ -# make chrome # same, but with Chrome's manifest, into build/ +# make # everything: build and package both browsers +# make firefox # build Firefox only -> build/firefox/ +# make chrome # build Chrome only -> build/chrome/ # make test # run the unit tests # 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/ +# make xpi # package Firefox -> dist/logsdu--firefox.xpi +# make crx # package Chrome -> dist/logsdu--chrome.zip +# make packages # both of the above (same as plain "make") +# make clean # remove build/, dist/ and stray packages +# make distclean # clean, plus node_modules/ # -# build/ holds the unpacked extension and is regenerated from scratch; dist/ +# build// holds the unpacked extension for one browser; the two targets +# never share a directory, so neither can be left stale by the other. dist/ # holds the packages meant to be published, and is kept out of build/ so that # packaging never tries to include its own output. # @@ -40,22 +43,33 @@ # # 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/ +# build/firefox/manifest.json (dropped when Firefox restarts) +# Chrome chrome://extensions -> Developer mode -> Load unpacked -> +# build/chrome/ # # Publishing: -# Firefox upload dist/*.xpi at addons.mozilla.org -# Chrome upload dist/*-chrome.zip at chrome.google.com/webstore/devconsole +# Firefox upload dist/*-firefox.xpi at addons.mozilla.org +# Chrome upload dist/*-chrome.zip at chrome.google.com/webstore/devconsole +# +# Every package filename names its browser. The two are not interchangeable -- +# they differ in the manifest's background key -- and uploading the wrong one +# fails in ways that are not obvious from the error. EXT_ID := logsdu VERSION := $(shell node -p "require('./package.json').version") +FIREFOX_DIR := build/firefox +CHROME_DIR := build/chrome DIST := dist -XPI := $(DIST)/$(EXT_ID)-$(VERSION).xpi +XPI := $(DIST)/$(EXT_ID)-$(VERSION)-firefox.xpi CRX := $(DIST)/$(EXT_ID)-$(VERSION)-chrome.zip -.PHONY: all build chrome test smoke xpi crx packages clean check-deps +.PHONY: all firefox chrome typecheck test smoke xpi crx packages clean \ + distclean check-deps -all: build +# The default does the lot: build both browsers and package both. Packaging is +# only a zip of a directory that was going to be built anyway, so making it the +# default costs nothing and means dist/ is never quietly out of date with src/. +all: packages # Unit tests for the pure logic: the input formatters and the decision that # says whether a page load may press "Entrar". Run straight through Node's @@ -64,30 +78,40 @@ 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 +# is where a Chrome-only breakage would otherwise hide until runtime. The +# bundle is identical for both targets, so checking one covers both. +smoke: chrome + node tools/sw-smoke.mjs $(CHROME_DIR)/background.js -# Call the local toolchain directly, so this works regardless of how pnpm is +# Typecheck once. Both build targets depend on it rather than each running tsc, +# which halves the work when building both. +# +# Calls the local toolchain directly, so this works regardless of how pnpm is # provided (corepack vs standalone). Run "corepack pnpm install" first. -build: check-deps +typecheck: check-deps node_modules/.bin/tsc -noEmit -skipLibCheck + +firefox: typecheck node esbuild.config.mjs production + @echo "Firefox build: $(CURDIR)/$(FIREFOX_DIR)" # 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 +chrome: typecheck TARGET=chrome node esbuild.config.mjs production - @echo "Chrome build in build/ -- chrome://extensions -> Developer mode -> Load unpacked." + @echo + @echo "Chrome build: $(CURDIR)/$(CHROME_DIR)" + @echo "Load it with chrome://extensions -> Developer mode -> Load unpacked." + @echo "Select the folder itself; Chrome wants the directory holding manifest.json." + @echo # An .xpi is just a zip of the extension directory, with the manifest at the # top level rather than inside a wrapper folder. -xpi: build +xpi: firefox @mkdir -p $(DIST) rm -f $(XPI) - cd build && zip -qr ../$(XPI) . + cd $(FIREFOX_DIR) && zip -qr $(CURDIR)/$(XPI) . @echo @echo "Built: $(CURDIR)/$(XPI)" @echo @@ -108,18 +132,30 @@ xpi: build crx: chrome @mkdir -p $(DIST) rm -f $(CRX) - cd build && zip -qr ../$(CRX) . + cd $(CHROME_DIR) && zip -qr $(CURDIR)/$(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 +# Both packages. Order no longer matters: each target has its own directory. +packages: xpi crx +# Removes everything the build produces, including packages from earlier +# versions, whose filenames carry their own version number and so are never +# overwritten by a later build. dist/ is tracked in git, so a clean shows the +# packages as deleted until the next "make packages" puts them back. clean: rm -rf build $(DIST) + rm -f $(EXT_ID)-*.xpi $(EXT_ID)-*.zip + find . -name '*.map' -not -path './node_modules/*' -delete + @echo "Removed build/, $(DIST)/ and any stray packages." + +# Everything clean removes, plus the installed dependencies. Recover with +# "corepack pnpm install" -- never with npm, see the note in README.md. +distclean: clean + rm -rf node_modules + @echo "Removed node_modules/. Run: corepack pnpm install" # Fail with a useful message rather than a confusing "tsc: not found". check-deps: diff --git a/README.md b/README.md index 15da879..264185c 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 dist/logsdu-.xpi +make xpi # bundle and package dist/logsdu--firefox.xpi ``` `make xpi` prints the full path of the file and how to install it. The package @@ -119,12 +119,12 @@ check. ## Chrome ``` -make chrome # unpacked Chrome build in build/ +make chrome # unpacked Chrome build in build/chrome/ make crx # package it as dist/logsdu--chrome.zip ``` -Load `build/` via `chrome://extensions` -> Developer mode -> **Load unpacked**, -or upload the zip at +Load `build/chrome/` via `chrome://extensions` -> Developer mode -> **Load +unpacked** (select the folder itself), 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* @@ -156,21 +156,29 @@ script instead. ``` corepack pnpm install +make # everything: build and package both browsers +make firefox # build Firefox only -> build/firefox/ +make chrome # build Chrome only -> build/chrome/ make test # unit tests make smoke # background bundle under a service worker -make # typecheck and bundle into build/ -make packages # both publishable packages into dist/ -corepack pnpm run dev # rebuild on change -make clean +corepack pnpm run dev # rebuild Firefox on change +make clean # remove build/, dist/ and stray packages +make distclean # clean, plus node_modules/ ``` -`build/` is the unpacked extension and is regenerated from scratch every time. -`dist/` holds the packages meant to be published, and is kept separate so that -packaging never tries to include its own output. +A bare `make` typechecks once, bundles for both browsers into `build/`, and +packages both into `dist/`. Packaging is only a zip of a directory that was +going to be built anyway, so it costs nothing and keeps `dist/` from drifting +out of step with the sources. + +Each browser gets its own directory under `build/`, so the two can coexist and +neither is ever left stale by the other. `dist/` holds the packages meant to be +published, and is kept out of `build/` so that packaging never tries to include +its own output. While iterating, load the unpacked directory rather than reinstalling an `.xpi` each time: `about:debugging` -> **This Firefox** -> **Load Temporary Add-on** -> -`build/manifest.json`, then press **Reload** there after each rebuild. That copy +`build/firefox/manifest.json`, then press **Reload** there after each rebuild. That copy disappears on restart, which is the point -- it is for development, not daily use. diff --git a/VERSION b/VERSION index 0d91a54..9e11b32 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.3.1 diff --git a/dist/logsdu-0.3.0-chrome.zip b/dist/logsdu-0.3.0-chrome.zip deleted file mode 100644 index 19692ba..0000000 Binary files a/dist/logsdu-0.3.0-chrome.zip and /dev/null differ diff --git a/dist/logsdu-0.3.0.xpi b/dist/logsdu-0.3.0.xpi deleted file mode 100644 index 3583969..0000000 Binary files a/dist/logsdu-0.3.0.xpi and /dev/null differ diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 7422852..0fc5881 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -35,12 +35,17 @@ const banner = `/* `; const prod = process.argv[2] === 'production'; -const outdir = 'build'; // Target browser. Firefox and Chrome disagree on exactly one manifest key, so // the manifest is written per target rather than duplicated in the tree. const target = process.env.TARGET === 'chrome' ? 'chrome' : 'firefox'; +// Each target gets its own directory. They used to share one, and the result +// was that whichever build ran last silently won: loading the other browser's +// output then failed with a confusing manifest error. Separate directories +// mean both can exist at once and neither can be stale by accident. +const outdir = `build/${target}`; + /** * Write the manifest for the target browser. * @@ -60,8 +65,9 @@ async function writeManifest() { ); } -// Everything that is not TypeScript is copied verbatim into build/, so that -// the directory can be handed straight to about:debugging. +// Everything that is not TypeScript is copied verbatim into the output, so +// that the directory can be handed straight to about:debugging or to Chrome's +// "Load unpacked". async function copyStatic() { await mkdir(outdir, { recursive: true }); for (const name of await readdir('src')) { @@ -76,7 +82,7 @@ async function copyStatic() { await copyStatic(); // In watch mode the static files must follow every rebuild, otherwise editing -// manifest.json or a .html file silently changes nothing in build/. +// manifest.json or a .html file silently changes nothing in the output. const staticPlugin = { name: 'copy-static', setup(build) { diff --git a/package.json b/package.json index 5471603..cdfeb53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "logsdu", - "version": "0.3.0", + "version": "0.3.1", "description": "Browser extension that fills and submits a three-field academic portal login.", "author": "Ruben Carlo Benante ", "type": "module", diff --git a/src/manifest.json b/src/manifest.json index 0e34fa1..6a79832 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "logsdu", - "version": "0.3.0", + "version": "0.3.1", "description": "Saves and fills logins that password managers cannot: registration number, date of birth and document number.", "author": "Ruben Carlo Benante (Dr. Beco)", "homepage_url": "https://code.beco.cc/beco/logsdu", diff --git a/tools/sw-smoke.mjs b/tools/sw-smoke.mjs index de67ed2..5ab6a0a 100644 --- a/tools/sw-smoke.mjs +++ b/tools/sw-smoke.mjs @@ -29,14 +29,15 @@ // // This evaluates the built bundle in a worker-shaped sandbox with a stubbed // extension API, and asserts that it registers exactly one content script, for -// exactly the configured origin. Run it against build/ after a build: +// exactly the configured origin. // // make smoke +// node tools/sw-smoke.mjs [path/to/background.js] import { readFileSync } from 'node:fs'; import vm from 'node:vm'; -const BUNDLE = 'build/background.js'; +const BUNDLE = process.argv[2] ?? 'build/chrome/background.js'; const CONFIGURED_ORIGIN = 'https://portal.example.br'; const permissionChecks = [];