diff --git a/docs/RELEASE.md b/docs/RELEASE.md index d911a37..1bd6ea8 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -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-.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 `` 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-.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.