Files
AutistMask/docs/RELEASE.md
T
sneak fd53737403
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
docs: second rework of the release procedure after review
Apply the five wording findings of the second review verbatim: the owner's
rule as one lead-in with three acts, the yarn source without a pronoun, the
workflow definition with its file first, the two e2e jobs named with their
scripts, and the manifest version tied to step 2. The same three faults
were fixed elsewhere: pronouns with two candidates, phrases that arrive
after the words they belong to, and terms used before they were defined.

Model: fable-5-1
2026-11-02 03:34:38 +00:00

229 lines
8.6 KiB
Markdown

# Releasing AutistMask
A release is a tagged commit on the `main` branch and the two archives that
`make package` writes from that commit. This file is the procedure, in order.
Only the owner ([@sneak](https://sneak.berlin)):
- decides whether and when a release is tagged;
- creates, moves or deletes a tag;
- decides where the archives are published.
Anyone can run steps 1 to 4. No script in this repository uploads or publishes
the archives. None signs them, and none packs a CRX (Chrome's packaged extension
file).
## Terms
- The tracker is the repository's Gitea site (Gitea is the software that hosts
the repository): https://git.eeqj.de/sneak/AutistMask. The tracker's Actions
page, https://git.eeqj.de/sneak/AutistMask/actions, lists the workflow runs.
- A workflow is a file under `.gitea/workflows/` that lists jobs. The tracker
runs every workflow on each push. Each job runs one script on the tracker's
runner (the machine that runs jobs).
- The release commit is a commit on `main`. Releases are tagged from `main` only
(`TODO.md`, Workflow).
- A clean checkout is a working tree (the checked-out files) at the release
commit where `git status --porcelain` prints nothing. The build stamps the
commit hash (the commit's id) into the extension and appends `-dirty` when the
tree has local changes.
- A tag is a name that git attaches to one commit. Release tags read `vX.Y.Z`,
such as `v0.1.0`.
- A digest is the sha256 hash of a file (a fixed-length fingerprint of its
bytes), written as 64 hexadecimal characters.
## What you need
- A clean checkout of the release commit.
- `make`.
- Docker (a tool that runs programs in containers), for `script/cibuild` in
step 1. `make package` needs no Docker.
- Node and yarn. Both come from the system when present. Otherwise
`script/bootstrap` installs Node through a pinned (fixed-version) install of
nvm, a Node version manager. `script/bootstrap` installs yarn through corepack
(Node's package manager installer). When no corepack is found,
`script/bootstrap` installs yarn through `npm` (Node's package manager).
## Procedure
### 1. Confirm the release commit is green
The `check` workflow (`.gitea/workflows/check.yml`) runs `script/cibuild` on
every push. `script/cibuild` runs:
- `script/bootstrap`, which installs the dependencies;
- `script/check`, which is what `make check` runs;
- the Docker image build (an image is Docker's packaged form of a program).
`script/check` runs:
- `script/test`: the tests, in Docker;
- `script/lint`, which runs three checks in Docker:
- ESLint, a code checker;
- `prettier --check .`, the formatting check;
- `script/check-censored`, which fails when the name that `RULES.md` bars
appears outside its allowed places;
- `script/fmt-check`: `prettier --check .` again, on the host.
The `e2e` workflow (`.gitea/workflows/e2e.yml`) has two jobs. One runs
`script/test-e2e`, the browser tests in Chrome. The other runs
`script/test-e2e-firefox`, the browser tests in Firefox. A red `e2e` job does
not fail the `check` run.
Run `script/cibuild` on the clean checkout. `script/cibuild` runs the same steps
as the `check` run, and must exit 0.
Check: on the Actions page, the `check` run for the release commit succeeded,
and both `e2e` jobs for that commit succeeded. `script/cibuild` exited 0 on the
clean checkout.
### 2. Confirm the version
Three files declare the version:
- `package.json`;
- `manifest/chrome.json`;
- `manifest/firefox.json`.
`script/lib/version.js` fails the build when the three disagree.
`tests/version.test.js` fails when the version is not three numbers separated by
dots. Nothing compares the version with a tag. That comparison is this step.
Check: all three files read the same `X.Y.Z`, and `vX.Y.Z` is the tag that the
owner creates in step 5.
### 3. Build and package
On the clean checkout, run:
```bash
make setup
make package
```
`make setup` installs two things:
- the dependencies, from the lockfile `yarn.lock`, which pins each one's
version;
- the git pre-commit hook, a script that git runs before each commit. Here the
hook runs `script/check`.
`make package` runs `make build`, then `script/lib/package.js`.
`make build`:
- runs `build.js`. `build.js` writes `dist/chrome/`, `dist/firefox/` and a build
receipt. The receipt lists every file that `build.js` wrote, with each file's
digest. The receipt lives outside the repository and is deleted when the build
ends;
- runs `script/verify-build --expect release` against the receipt. The check
fails when:
- `dist/` differs from the receipt;
- a bundle (one JavaScript file built from many source files) was compiled
with the build-time constant `DEBUG` on;
- any file carries the test recovery phrase, which a debug build gives every
new wallet;
- runs `script/check-censored --require-dist` against the built files;
- removes `dist/` when any of those steps fails.
`script/lib/package.js`:
- empties `release/`;
- refuses a symbolic link (a file that points at another file) under `dist/`;
- checks that the archive is self-contained: every file that the manifest or the
popup's HTML page names is inside the browser's directory (`dist/chrome/` or
`dist/firefox/`). The manifest is `manifest.json`, the file that describes the
extension to the browser. The popup is the window that the toolbar button
opens;
- checks that the `version` in each `manifest.json` is the version from step 2;
- writes one archive per browser;
- reads each archive back;
- compares every member (a file inside the archive) with `dist/` byte for byte;
- writes `SHA256SUMS`, with one line per archive:
- the digest;
- two spaces;
- the file name.
`make package` writes three files:
```
release/autistmask-chrome-<version>.zip
release/autistmask-firefox-<version>.xpi
release/SHA256SUMS
```
The `.xpi` is Firefox's add-on package file (an add-on is Firefox's word for an
extension). The `.xpi` is not signed.
`make package` prints a line that starts `Not shipped` and names `styles.css`.
This is expected: each archive holds its own copy of the stylesheet.
Check: `release/` holds those three files, and `<version>` in their names is the
version from step 2.
### 4. Verify the digests
From `release/`, run `sha256sum -c SHA256SUMS`.
The archives are deterministic for a given `dist/`: the same `dist/` always
gives the same bytes. `script/lib/zip.js`:
- sorts the members;
- sets every member's timestamp (its recorded date) to 1980-01-01;
- compresses each member the same way every time.
`dist/` itself carries the build date. `build.js` compiles the build date, a UTC
(universal time) day, into the extension. So two builds of the same commit on
the same UTC day give byte-identical archives. Builds on different days differ.
Check: `sha256sum -c` prints `OK` for both archives. On the same UTC day, a
second `make package` from another clean checkout of the same commit gives the
same `SHA256SUMS`.
### 5. Tag the release commit
Owner only. Only the owner decides whether and when a release is tagged. Only
the owner creates, moves or deletes a tag. On the release commit, the owner
runs:
```bash
git tag -a vX.Y.Z -m "AutistMask X.Y.Z"
git push origin vX.Y.Z
```
`-a` makes an annotated tag: a tag that carries a message and records who made
the tag and when. `v0.1.0` is one.
Once the tag exists, `git describe --tags` on the release commit prints
`vX.Y.Z`. `script/cibuild` and `script/docker` pass that value to the Docker
image build as `VERSION`.
Check: `git rev-parse vX.Y.Z^{commit}` prints the release commit's hash, and the
tag is listed at https://git.eeqj.de/sneak/AutistMask/tags.
### 6. Publish the archives
Owner only. The owner decides where the archives are published. No script in
this repository uploads or publishes them.
Whoever receives an archive installs it as follows.
Chrome loads the extension from a directory:
- unzip the archive into a new, empty directory, for example
`unzip autistmask-chrome-0.1.0.zip -d autistmask-chrome`;
- open `chrome://extensions/` and turn on "Developer mode";
- click "Load unpacked" and pick that directory.
Chrome then reports the extension id `gipbhkogfopeahplcjhipkgpcimdpkip`. The id
is the fixed string that identifies the extension to Chrome; the `key` in
`manifest/chrome.json` sets the id.
Release builds of Firefox refuse to install an unsigned add-on. Desktop Firefox
115 or newer, in any edition, loads the `.xpi` as a temporary add-on from the
`about:debugging` page. Firefox unloads a temporary add-on when Firefox exits.
The section "Installing on Firefox" in `README.md` has the details.
Check: each archive installs on a fresh browser profile (a new, empty set of
browser data) by the route above.