# Releasing AutistMask 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)). ## 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). - 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 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. ## 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). ## 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 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`; - the Docker image build. 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. 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. ### 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 step 5 creates. ### 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`, which writes `dist/chrome/` and `dist/firefox/`, and a build receipt: the list of 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 fixed recovery phrase that a debug build hands out; - 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 every file named by the manifest or the popup markup is inside the browser's directory; - 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. `make package` writes three files: ``` release/autistmask-chrome-.zip 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/`. Check: `release/` holds those three files, and `` 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/`: `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. 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 same `SHA256SUMS`. ### 5. Tag the release commit Owner only. On the release commit, run: ```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. 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. ### 6. Hand out the archives The archives in `release/` are the release. This repository has no further step. Chrome loads the extension from a directory: - unzip `autistmask-chrome-.zip`; - open `chrome://extensions/` and turn on "Developer mode"; - click "Load unpacked" and pick the unzipped directory. Chrome then reports the extension id `gipbhkogfopeahplcjhipkgpcimdpkip`, which the `key` in `manifest/chrome.json` fixes. 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. Check: each archive installs on a fresh browser profile by the route above.