docs: rework the release procedure after review
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:
+94
-56
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user