2 Commits

Author SHA1 Message Date
825fedb366 release 0.3.2 2026-08-11 11:23:04 -03:00
5176301116 chrome+firefox v0.3.1 Makefile and dist/ build/ folders 2026-08-10 16:14:56 -03:00
9 changed files with 114 additions and 52 deletions

109
Makefile
View File

@@ -22,16 +22,20 @@
# Makefile for logsdu - build the extension for Firefox and Chrome. # Makefile for logsdu - build the extension for Firefox and Chrome.
# #
# Usage: # Usage:
# make # typecheck and bundle for Firefox into build/ # make # everything: build and package both browsers
# make chrome # same, but with Chrome's manifest, into build/ # make firefox # build Firefox only -> build/firefox/
# make chrome # build Chrome only -> build/chrome/
# make test # run the unit tests # make test # run the unit tests
# make smoke # check the background bundle works as a service worker # make smoke # check the background bundle works as a service worker
# make xpi # package the Firefox build -> dist/logsdu-<version>.xpi # make xpi # package Firefox -> dist/logsdu-<version>-firefox.xpi
# make crx # package the Chrome build -> dist/logsdu-<version>-chrome.zip # make crx # package Chrome -> dist/logsdu-<version>-chrome.zip
# make packages # both of the above # make packages # both of the above (same as plain "make")
# make clean # remove build/ and dist/ # make prune # drop packages left in dist/ by earlier versions
# 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/<target>/ 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 # holds the packages meant to be published, and is kept out of build/ so that
# packaging never tries to include its own output. # packaging never tries to include its own output.
# #
@@ -40,22 +44,33 @@
# #
# Load the unpacked build while developing: # Load the unpacked build while developing:
# Firefox about:debugging -> This Firefox -> Load Temporary Add-on -> # Firefox about:debugging -> This Firefox -> Load Temporary Add-on ->
# build/manifest.json (dropped when Firefox restarts) # build/firefox/manifest.json (dropped when Firefox restarts)
# Chrome chrome://extensions -> Developer mode -> Load unpacked -> build/ # Chrome chrome://extensions -> Developer mode -> Load unpacked ->
# build/chrome/
# #
# Publishing: # Publishing:
# Firefox upload dist/*.xpi at addons.mozilla.org # Firefox upload dist/*-firefox.xpi at addons.mozilla.org
# Chrome upload dist/*-chrome.zip at chrome.google.com/webstore/devconsole # 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 EXT_ID := logsdu
VERSION := $(shell node -p "require('./package.json').version") VERSION := $(shell node -p "require('./package.json').version")
FIREFOX_DIR := build/firefox
CHROME_DIR := build/chrome
DIST := dist DIST := dist
XPI := $(DIST)/$(EXT_ID)-$(VERSION).xpi XPI := $(DIST)/$(EXT_ID)-$(VERSION)-firefox.xpi
CRX := $(DIST)/$(EXT_ID)-$(VERSION)-chrome.zip 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 prune 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 # 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 # says whether a page load may press "Entrar". Run straight through Node's
@@ -64,30 +79,51 @@ test:
node --test "src/**/*.test.ts" node --test "src/**/*.test.ts"
# Runs the built background bundle in a service-worker-shaped sandbox, which # Runs the built background bundle in a service-worker-shaped sandbox, which
# is where a Chrome-only breakage would otherwise hide until runtime. # is where a Chrome-only breakage would otherwise hide until runtime. The
smoke: build # bundle is identical for both targets, so checking one covers both.
node tools/sw-smoke.mjs 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. # provided (corepack vs standalone). Run "corepack pnpm install" first.
build: check-deps typecheck: check-deps
node_modules/.bin/tsc -noEmit -skipLibCheck node_modules/.bin/tsc -noEmit -skipLibCheck
firefox: typecheck
node esbuild.config.mjs production node esbuild.config.mjs production
@echo "Firefox build: $(CURDIR)/$(FIREFOX_DIR)"
# The same sources with Chrome's manifest. Firefox and Chrome disagree on the # 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 # background key and on the gecko block, so the manifest is generated per
# target rather than forked. # target rather than forked.
chrome: check-deps chrome: typecheck
node_modules/.bin/tsc -noEmit -skipLibCheck
TARGET=chrome node esbuild.config.mjs production 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
# Drop packages left over from earlier versions.
#
# Package names carry their version, so a bump does not overwrite the previous
# build -- without this, dist/ accumulates every version ever built and it
# becomes easy to upload the wrong file to a store. dist/ is regenerated
# output: keep signed downloads from AMO somewhere else, not here.
prune:
@mkdir -p $(DIST)
@find $(DIST) -maxdepth 1 -type f \( -name '$(EXT_ID)-*.xpi' -o -name '$(EXT_ID)-*.zip' \) \
! -name '$(notdir $(XPI))' ! -name '$(notdir $(CRX))' \
-print -delete | sed 's/^/removed stale package: /'
# An .xpi is just a zip of the extension directory, with the manifest at the # An .xpi is just a zip of the extension directory, with the manifest at the
# top level rather than inside a wrapper folder. # top level rather than inside a wrapper folder.
xpi: build xpi: firefox prune
@mkdir -p $(DIST)
rm -f $(XPI) rm -f $(XPI)
cd build && zip -qr ../$(XPI) . cd $(FIREFOX_DIR) && zip -qr $(CURDIR)/$(XPI) .
@echo @echo
@echo "Built: $(CURDIR)/$(XPI)" @echo "Built: $(CURDIR)/$(XPI)"
@echo @echo
@@ -105,21 +141,32 @@ xpi: build
@echo @echo
# The Chrome Web Store takes a plain zip, and does the packing into .crx itself. # The Chrome Web Store takes a plain zip, and does the packing into .crx itself.
crx: chrome crx: chrome prune
@mkdir -p $(DIST)
rm -f $(CRX) rm -f $(CRX)
cd build && zip -qr ../$(CRX) . cd $(CHROME_DIR) && zip -qr $(CURDIR)/$(CRX) .
@echo @echo
@echo "Built: $(CURDIR)/$(CRX)" @echo "Built: $(CURDIR)/$(CRX)"
@echo "Upload it at chrome.google.com/webstore/devconsole" @echo "Upload it at chrome.google.com/webstore/devconsole"
@echo @echo
# Both packages. The Firefox build runs last so build/ is left in the state # Both packages. Order no longer matters: each target has its own directory.
# the development instructions assume. packages: xpi crx
packages: crx xpi
# 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: clean:
rm -rf build $(DIST) 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". # Fail with a useful message rather than a confusing "tsc: not found".
check-deps: check-deps:

View File

@@ -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 corepack pnpm install # first time, or after a dependency change
make xpi # typecheck, bundle, and package dist/logsdu-<version>.xpi make xpi # bundle and package dist/logsdu-<version>-firefox.xpi
``` ```
`make xpi` prints the full path of the file and how to install it. The package `make xpi` prints the full path of the file and how to install it. The package
@@ -119,12 +119,12 @@ check.
## Chrome ## Chrome
``` ```
make chrome # unpacked Chrome build in build/ make chrome # unpacked Chrome build in build/chrome/
make crx # package it as dist/logsdu-<version>-chrome.zip make crx # package it as dist/logsdu-<version>-chrome.zip
``` ```
Load `build/` via `chrome://extensions` -> Developer mode -> **Load unpacked**, Load `build/chrome/` via `chrome://extensions` -> Developer mode -> **Load
or upload the zip at unpacked** (select the folder itself), or upload the zip at
[the Web Store dashboard](https://chrome.google.com/webstore/devconsole). [the Web Store dashboard](https://chrome.google.com/webstore/devconsole).
One codebase, two manifests. Chrome MV3 requires a background *service worker* One codebase, two manifests. Chrome MV3 requires a background *service worker*
@@ -156,21 +156,29 @@ script instead.
``` ```
corepack pnpm install 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 test # unit tests
make smoke # background bundle under a service worker make smoke # background bundle under a service worker
make # typecheck and bundle into build/ corepack pnpm run dev # rebuild Firefox on change
make packages # both publishable packages into dist/ make clean # remove build/, dist/ and stray packages
corepack pnpm run dev # rebuild on change make distclean # clean, plus node_modules/
make clean
``` ```
`build/` is the unpacked extension and is regenerated from scratch every time. A bare `make` typechecks once, bundles for both browsers into `build/`, and
`dist/` holds the packages meant to be published, and is kept separate so that packages both into `dist/`. Packaging is only a zip of a directory that was
packaging never tries to include its own output. 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` While iterating, load the unpacked directory rather than reinstalling an `.xpi`
each time: `about:debugging` -> **This Firefox** -> **Load Temporary Add-on** -> 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 disappears on restart, which is the point -- it is for development, not daily
use. use.

View File

@@ -1 +1 @@
0.3.0 0.3.2

View File

@@ -35,12 +35,17 @@ const banner = `/*
`; `;
const prod = process.argv[2] === 'production'; const prod = process.argv[2] === 'production';
const outdir = 'build';
// Target browser. Firefox and Chrome disagree on exactly one manifest key, so // Target browser. Firefox and Chrome disagree on exactly one manifest key, so
// the manifest is written per target rather than duplicated in the tree. // the manifest is written per target rather than duplicated in the tree.
const target = process.env.TARGET === 'chrome' ? 'chrome' : 'firefox'; 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. * 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 // Everything that is not TypeScript is copied verbatim into the output, so
// the directory can be handed straight to about:debugging. // that the directory can be handed straight to about:debugging or to Chrome's
// "Load unpacked".
async function copyStatic() { async function copyStatic() {
await mkdir(outdir, { recursive: true }); await mkdir(outdir, { recursive: true });
for (const name of await readdir('src')) { for (const name of await readdir('src')) {
@@ -76,7 +82,7 @@ async function copyStatic() {
await copyStatic(); await copyStatic();
// In watch mode the static files must follow every rebuild, otherwise editing // 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 = { const staticPlugin = {
name: 'copy-static', name: 'copy-static',
setup(build) { setup(build) {

View File

@@ -1,6 +1,6 @@
{ {
"name": "logsdu", "name": "logsdu",
"version": "0.3.0", "version": "0.3.2",
"description": "Browser extension that fills and submits a three-field academic portal login.", "description": "Browser extension that fills and submits a three-field academic portal login.",
"author": "Ruben Carlo Benante <rcb@beco.cc>", "author": "Ruben Carlo Benante <rcb@beco.cc>",
"type": "module", "type": "module",

View File

@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "logsdu", "name": "logsdu",
"version": "0.3.0", "version": "0.3.2",
"description": "Saves and fills logins that password managers cannot: registration number, date of birth and document number.", "description": "Saves and fills logins that password managers cannot: registration number, date of birth and document number.",
"author": "Ruben Carlo Benante (Dr. Beco)", "author": "Ruben Carlo Benante (Dr. Beco)",
"homepage_url": "https://code.beco.cc/beco/logsdu", "homepage_url": "https://code.beco.cc/beco/logsdu",

View File

@@ -29,14 +29,15 @@
// //
// This evaluates the built bundle in a worker-shaped sandbox with a stubbed // 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 // 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 // make smoke
// node tools/sw-smoke.mjs [path/to/background.js]
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import vm from 'node:vm'; 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 CONFIGURED_ORIGIN = 'https://portal.example.br';
const permissionChecks = []; const permissionChecks = [];