docs: rework the release procedure after review
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s

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` 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. writes from that commit. This file is the procedure, in order.
Nothing in this repository publishes. There is no store upload, no signing and The owner ([@sneak](https://sneak.berlin)) alone decides three things:
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)). - 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 ## Terms
- The release commit is the commit on `main` to be released. `main` receives one - The tracker is the repository's Gitea site (Gitea is the software that hosts
pull request per milestone (the tracker's set of issues for one version), from the repository): https://git.eeqj.de/sneak/AutistMask. Its Actions page,
`next`, and releases are tagged from `main` (`TODO.md`, Workflow). 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 - A clean checkout is a working tree at the release commit where
`git status --porcelain` prints nothing. The build stamps the commit hash into `git status --porcelain` prints nothing. The build stamps the commit hash into
the extension and appends `-dirty` when the tree has local changes. 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 - A tag is a name that git attaches to one commit. Release tags read `vX.Y.Z`,
as `v0.1.0`. such as `v0.1.0`.
- A digest is the sha256 hash of a file (a fixed-length fingerprint of its - A digest is the sha256 hash of a file (a fixed-length fingerprint of its
bytes), written as 64 hex characters. bytes), written as 64 hexadecimal 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.
## What you need ## What you need
- A clean checkout of the release commit. - A clean checkout of the release commit.
- `make`. - `make`.
- Docker, for `make check`. `make package` needs no Docker. - Docker (a tool that runs programs in containers), for `script/cibuild` in
- Node and yarn. Both come from the system when present. Otherwise `make setup` step 1. `make package` needs no Docker.
installs Node through a pinned install of nvm (a Node version manager) and - Node and yarn. Both come from the system when present. Otherwise
yarn through corepack (Node's package manager installer). `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 ## Procedure
@@ -40,17 +50,28 @@ The `check` workflow (`.gitea/workflows/check.yml`) runs `script/cibuild` on
every push. `script/cibuild` runs: every push. `script/cibuild` runs:
- `script/bootstrap`, which installs the dependencies; - `script/bootstrap`, which installs the dependencies;
- `script/check`, which is what `make check` runs: the tests, the lint checks - `script/check`, which is what `make check` runs;
and the formatting check. The lint checks are ESLint (a code checker), the
prettier check (a formatting check) and `script/check-censored`;
- the Docker image build. - 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 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. 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, 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 and both `e2e` jobs for that commit succeeded. `script/cibuild` exited 0 on the
checkout gives the same answer as the `check` run. clean checkout.
### 2. Confirm the version ### 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 `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. 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 Check: all three files read the same `X.Y.Z`, and `vX.Y.Z` is the tag that the
5 creates. owner creates in step 5.
### 3. Build and package ### 3. Build and package
@@ -87,8 +108,8 @@ make package
`make build`: `make build`:
- runs `build.js`, which writes `dist/chrome/` and `dist/firefox/`, and a build - runs `build.js`. It writes `dist/chrome/`, `dist/firefox/` and a build
receipt: the list of every file that `build.js` wrote, with each file's 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 digest. The receipt lives outside the repository and is deleted when the build
ends; ends;
- runs `script/verify-build --expect release` against the receipt. The check - runs `script/verify-build --expect release` against the receipt. The check
@@ -96,7 +117,8 @@ make package
- `dist/` differs from the receipt; - `dist/` differs from the receipt;
- a bundle (one JavaScript file built from many source files) was compiled - a bundle (one JavaScript file built from many source files) was compiled
with the build-time constant `DEBUG` on; 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; - runs `script/check-censored --require-dist` against the built files;
- removes `dist/` when any of those steps fails. - removes `dist/` when any of those steps fails.
@@ -104,13 +126,17 @@ make package
- empties `release/`; - empties `release/`;
- refuses a symbolic link (a file that points at another file) under `dist/`; - 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 - checks that the archive is self-contained: every file that the manifest or the
browser's directory; 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; - 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 - writes one archive per browser;
file inside the archive) with `dist/` byte for byte; - reads each archive back;
- writes `SHA256SUMS`: one line per archive with the digest, two spaces and the - compares every member (a file inside the archive) with `dist/` byte for byte;
file name. - writes `SHA256SUMS`, with one line per archive:
- the digest;
- two spaces;
- the file name.
`make package` writes three files: `make package` writes three files:
@@ -120,10 +146,11 @@ release/autistmask-firefox-<version>.xpi
release/SHA256SUMS release/SHA256SUMS
``` ```
The `.xpi` is Firefox's add-on package file. It is not signed. The output line The `.xpi` is Firefox's add-on package file (an add-on is Firefox's word for an
`Not shipped` names `dist/styles.css`. `build.js` writes the compiled stylesheet extension). It is not signed.
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/`. `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 Check: `release/` holds those three files, and `<version>` in their names is the
version from step 2. version from step 2.
@@ -132,12 +159,15 @@ version from step 2.
From `release/`, run `sha256sum -c SHA256SUMS`. From `release/`, run `sha256sum -c SHA256SUMS`.
The archives are deterministic for a given `dist/`: `script/lib/zip.js` sorts The archives are deterministic for a given `dist/`. `script/lib/zip.js`:
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, - sorts the members;
in UTC (universal time), into the extension, with the commit hash and the - sets every timestamp to 1980-01-01;
version. So two builds of the same commit on the same UTC day give - compresses each member the same way every time.
byte-identical archives. Builds on different days differ.
`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` 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 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 ### 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 ```bash
git tag -a vX.Y.Z -m "AutistMask X.Y.Z" git tag -a vX.Y.Z -m "AutistMask X.Y.Z"
git push origin vX.Y.Z git push origin vX.Y.Z
``` ```
`-a` makes an annotated tag: a tag object with its own message, author and date. `-a` makes an annotated tag: a tag that carries a message and records who made
`v0.1.0` is one. it and when. `v0.1.0` is one.
Once the tag exists, `git describe --tags` on the release commit prints 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 `vX.Y.Z`. `script/cibuild` and `script/docker` pass that value to the Docker
image build as `VERSION`. image build as `VERSION`.
Check: `git rev-parse vX.Y.Z^{commit}` prints the release commit's hash, and the 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: 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"; - 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 Chrome then reports the extension id `gipbhkogfopeahplcjhipkgpcimdpkip`. The id
the `key` in `manifest/chrome.json` fixes. 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 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 115 or newer, in any edition, loads the `.xpi` as a temporary add-on from its
`about:debugging` and unloads it when Firefox exits. The section "Installing on `about:debugging` page. Firefox unloads a temporary add-on when Firefox exits.
Firefox" in `README.md` has the details. 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.