docs: rework the release procedure after review
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

Fix the eight findings of the first review: step 1 now runs script/cibuild
on the clean checkout instead of make check; the owner's rule on tagging and
publishing is stated in the opening and in steps 5 and 6; the Chrome unzip
goes into a new directory; yarn comes from corepack or npm; the comma lists
are bullets; the prettier check is named once with its two places; the
re-read sentences are rewritten; the tracker and its Actions page are named
with their links. A fact pass ran every command of steps 2 to 4 and the
unzip on a fresh clone, and a definition pass glossed each technical word.

Model: fable-5-1
This commit is contained in:
2026-11-02 03:16:17 +00:00
parent 69ce8fd6d6
commit c45cf628a9
+94 -56
View File
@@ -3,34 +3,44 @@
A release is a tagged commit on `main` and the two archives that `make package`
writes from that commit. This file is the procedure, in order.
Nothing in this repository publishes. There is no store upload, no signing and
no CRX (a CRX is Chrome's packaged extension file). Anyone can run the build and
the packaging. Tagging is the owner's job ([@sneak](https://sneak.berlin)).
The owner ([@sneak](https://sneak.berlin)) alone decides three things:
- whether and when a release is tagged;
- the tags: only the owner creates, moves or deletes a tag;
- 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 release commit is the commit on `main` to be released. `main` receives one
pull request per milestone (the tracker's set of issues for one version), from
`next`, and releases are tagged from `main` (`TODO.md`, Workflow).
- The tracker is the repository's Gitea site (Gitea is the software that hosts
the repository): https://git.eeqj.de/sneak/AutistMask. Its Actions page,
https://git.eeqj.de/sneak/AutistMask/actions, lists the workflow runs.
- A workflow is a list of jobs that the tracker runs on every push, from a file
under `.gitea/workflows/`. 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 at the release commit where
`git status --porcelain` prints nothing. The build stamps the commit hash into
the extension and appends `-dirty` when the tree has local changes.
- A tag is a name that git fixes to one commit. Release tags read `vX.Y.Z`, such
as `v0.1.0`.
- 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 hex characters.
- A workflow is a list of jobs that the tracker runs on every push, from a file
under `.gitea/workflows/`. The tracker lists the runs on the repository's
Actions page.
bytes), written as 64 hexadecimal characters.
## What you need
- A clean checkout of the release commit.
- `make`.
- Docker, for `make check`. `make package` needs no Docker.
- Node and yarn. Both come from the system when present. Otherwise `make setup`
installs Node through a pinned install of nvm (a Node version manager) and
yarn through corepack (Node's package manager installer).
- 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. It installs yarn through corepack (Node's package
manager installer) or, failing that, through `npm` (Node's package manager).
## Procedure
@@ -40,17 +50,28 @@ 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 tests, the lint checks
and the formatting check. The lint checks are ESLint (a code checker), the
prettier check (a formatting check) and `script/check-censored`;
- `script/check`, which is what `make check` runs;
- the Docker image build.
`script/check` runs:
- `script/test`: the tests, in Docker;
- `script/lint`, in Docker, which runs three checks:
- 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`) runs the two browser suites on
every push, as two jobs. A red `e2e` job does not fail the `check` run.
Run `script/cibuild` on the clean checkout. It runs the same steps as the
`check` run, and it must exit 0.
Check: on the Actions page, the `check` run for the release commit succeeded,
and both `e2e` jobs for that commit succeeded. `make check` on the clean
checkout gives the same answer as the `check` run.
and both `e2e` jobs for that commit succeeded. `script/cibuild` exited 0 on the
clean checkout.
### 2. Confirm the version
@@ -64,8 +85,8 @@ Three files declare the version:
`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 step
5 creates.
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
@@ -87,8 +108,8 @@ make package
`make build`:
- runs `build.js`, which writes `dist/chrome/` and `dist/firefox/`, and a build
receipt: the list of every file that `build.js` wrote, with each file's
- runs `build.js`. It 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
@@ -96,7 +117,8 @@ make package
- `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 fixed recovery phrase that a debug build hands out;
- 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.
@@ -104,13 +126,17 @@ make package
- empties `release/`;
- refuses a symbolic link (a file that points at another file) under `dist/`;
- checks that every file named by the manifest or the popup markup is inside the
browser's directory;
- 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. The manifest is
`manifest.json`, the file that describes the extension to the browser;
- checks that the `version` in each `manifest.json` is the tree's version;
- writes one archive per browser, reads it back and compares every member (a
file inside the archive) with `dist/` byte for byte;
- writes `SHA256SUMS`: one line per archive with the digest, two spaces and the
file name.
- 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:
@@ -120,10 +146,11 @@ release/autistmask-firefox-<version>.xpi
release/SHA256SUMS
```
The `.xpi` is Firefox's add-on package file. It is not signed. The output line
`Not shipped` names `dist/styles.css`. `build.js` writes the compiled stylesheet
there and copies it into each browser directory. The copy at the root of `dist/`
is in neither archive. `make clean` removes `dist/` and `release/`.
The `.xpi` is Firefox's add-on package file (an add-on is Firefox's word for an
extension). It 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.
@@ -132,12 +159,15 @@ version from step 2.
From `release/`, run `sha256sum -c SHA256SUMS`.
The archives are deterministic for a given `dist/`: `script/lib/zip.js` sorts
the members, sets every timestamp to 1980-01-01 and fixes the compression level.
`dist/` itself carries the build date: `build.js` compiles the day of the build,
in UTC (universal time), into the extension, with the commit hash and the
version. So two builds of the same commit on the same UTC day give
byte-identical archives. Builds on different days differ.
The archives are deterministic for a given `dist/`. `script/lib/zip.js`:
- sorts the members;
- sets every timestamp 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. A second `make package`
from another clean checkout of the same commit, on the same UTC day, gives the
@@ -145,39 +175,47 @@ same `SHA256SUMS`.
### 5. Tag the release commit
Owner only. On the release commit, run:
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 object with its own message, author and date.
`v0.1.0` is one.
`-a` makes an annotated tag: a tag that carries a message and records who made
it 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
tracker lists the tag under the repository's tags.
tag is listed at https://git.eeqj.de/sneak/AutistMask/tags.
### 6. Hand out the archives
### 6. Publish the archives
The archives in `release/` are the release. This repository has no further step.
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 `autistmask-chrome-<version>.zip`;
- 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 the unzipped directory.
- click "Load unpacked" and pick that directory.
Chrome then reports the extension id `gipbhkogfopeahplcjhipkgpcimdpkip`, which
the `key` in `manifest/chrome.json` fixes.
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 it.
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
`about:debugging` and unloads it when Firefox exits. The section "Installing on
Firefox" in `README.md` has the details.
115 or newer, in any edition, loads the `.xpi` as a temporary add-on from its
`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 by the route above.
Check: each archive installs on a fresh browser profile (a new, empty set of
browser data) by the route above.