Build images / build-ml (push) Successful in 4s
CI / lint (push) Successful in 4s
Build images / build-agent (push) Successful in 5s
CI / extension-version (push) Successful in 4s
CI / frontend-build (push) Successful in 22s
extension / lint (push) Successful in 22s
CI / backend-lint-and-test (push) Failing after 33s
Build images / sign-extension (push) Successful in 2m24s
Build images / build-web (push) Successful in 2m38s
CI / integration (push) Successful in 5m15s
`1.0.<minutes since 2020>` -> `YYYY.M.D.HHMM` UTC, from the commit time of
the newest change to a packaged extension file. Same clock and same commit as
before; readable instead of opaque, and the same value the rest of the family
derives.
The hold on this step was two questions about AMO, and Mozilla's own docs
answer both:
^(0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}$
1. four all-numeric segments -> ACCEPTED ({0,3} more after the first).
2. leading zeros -> REJECTED. A segment is the single digit
`0` or starts 1-9, so `08` and `0201` are refused. MDN says it in prose
too: "Non-zero numbers must not include a leading zero."
So the documented fallback applies, extension only: the same numbers rendered
without the family's zero-padding. `2026.08.29.0201` and `2026.8.29.201` are
one value in two renderings — rule 148 defines comparison as numeric per
segment, under which they are equal — so nothing already published is
reordered, and left-padding each segment recovers the family string exactly.
HHMM stays one segment because AMO allows at most four.
The transition is safe in the other direction too: 2026 > 1, so every CalVer
outranks every published 1.0.x. build.yml's downgrade guard confirms it.
Also in scope:
* MAJOR.MINOR is gone. `cmd_major_minor`, `cmd_patch` and VERSION_EPOCH go
with it, the committed version in manifest.json / package.json is now
wholly inert, and ci.yml's MAJOR.MINOR-agreement check is retired rather
than left running beside a fact that stopped existing (rule 22).
* ci.yml's `extension-version` lane now asserts Mozilla's regex verbatim
instead of a loose `^[0-9]+(\.[0-9]+)*$` — which would have passed the
padded shape. It also asserts YYYY.M.D.HHMM, because AMO would accept a
regression to `1.0.<minutes>` while that orders below everything signed
since. Checking here is the point: AMO 409s on re-signing, so a version it
rejects is burned and cannot be reused.
* `artifacts.sh version extension` delegates to packaging.sh, so the two
cannot answer differently. The direction matches the existing one —
artifacts.sh already asks packaging.sh for the extension's path set.
#3156 is what makes this commit safe to make: packaging.sh is in web's path
set, so the web revision moves with the extension version and build-web
rebuilds instead of republishing an image bundling the previous XPI.
Scribe #3138.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
114 lines
5.4 KiB
Markdown
114 lines
5.4 KiB
Markdown
# FabledCurator Firefox Extension
|
|
|
|
Self-hosted Firefox extension that pushes session cookies from supported
|
|
platforms (Patreon, SubscribeStar, Hentai-Foundry, Discord, Pixiv)
|
|
into FabledCurator, and lets you add a creator as a Source from their
|
|
page in one click.
|
|
|
|
## Install (operator)
|
|
|
|
The signed XPI is bundled into the FC Docker image — `:dev` and
|
|
`:latest` each carry their own channel's build. Open FC →
|
|
Settings → Maintenance → Browser extension → click "Install Firefox
|
|
extension". Firefox shows its native install prompt. After installing,
|
|
open the extension's options page (about:addons → FabledCurator →
|
|
Preferences) and paste in the FC URL + extension API key shown on the
|
|
same card.
|
|
|
|
## Develop
|
|
|
|
```sh
|
|
cd extension/
|
|
npm install --no-save # web-ext only
|
|
npm run lint # web-ext lint
|
|
npm run test:unit # vitest — lib/ logic + packaging/version checks
|
|
npm run start # launches Firefox with extension loaded
|
|
npm run build # unsigned XPI in web-ext-artifacts/
|
|
```
|
|
|
|
## Smoke checklist (after every release that touches `extension/**`)
|
|
|
|
- [ ] `npm run lint` passes
|
|
- [ ] `npm run start` loads the extension in a clean Firefox profile
|
|
- [ ] Options page accepts FC URL + key, indicator turns green
|
|
- [ ] Cookie export: log into patreon.com, click Patreon card → "X cookies exported"
|
|
- [ ] Discord token: open discord.com, click Discord card → "Token captured"
|
|
- [ ] Pixiv OAuth: click Pixiv card → login redirects, token stored
|
|
- [ ] Add as source: visit patreon.com/<creator>, click floating button → toast
|
|
- [ ] Subscriptions list: popup → "Sources" tab → list renders
|
|
- [ ] Check now: click play icon on source row → no error toast
|
|
|
|
## Versioning — the committed number decides nothing
|
|
|
|
The shipped version is **derived**, not committed. `scripts/packaging.sh
|
|
version` returns `YYYY.M.D.HHMM` in UTC: the commit *time* of the newest change
|
|
to a packaged extension file. `build.yml` computes it and stamps it into both
|
|
`manifest.json` and `package.json` at build time. The stamp is never
|
|
committed — the commit carrying it would itself be a change to the extension,
|
|
which would move the version again.
|
|
|
|
So:
|
|
|
|
- **Editing the version does nothing.** All of it is overwritten before web-ext
|
|
ever reads it. There is no bump to make, and none to forget. There is no
|
|
hand-set part left either: MAJOR.MINOR went away with milestone 318 step 8.
|
|
- `npm run build` locally produces an XPI labelled with the *committed*
|
|
version, since nothing stamped it. Fine for loading into a test profile; not
|
|
what ships.
|
|
|
|
**Why the extension is the one artifact that does not zero-pad.** Every other
|
|
FC artifact emits rule 148's `YYYY.MM.DD.HHMM`. AMO will not take it: Mozilla's
|
|
grammar for addons.mozilla.org is
|
|
|
|
```
|
|
^(0|[1-9][0-9]{0,8})([.](0|[1-9][0-9]{0,8})){0,3}$
|
|
```
|
|
|
|
— each segment is the single digit `0` or starts 1-9, so `08` and `0201` are
|
|
rejected, and at most four segments are allowed. The extension therefore emits
|
|
**the same numbers unpadded**: `2026.8.29.201` where the rest of the family
|
|
says `2026.08.29.0201`. Rule 148 already defines comparison as numeric per
|
|
segment, under which the two are equal, so nothing is reordered by the choice
|
|
and left-padding each segment recovers the family string exactly. `ci.yml`'s
|
|
`extension-version` lane checks the derived string against that regex on every
|
|
push — the cheap place to find out, because AMO 409s on re-signing and a
|
|
rejected version is burned for good.
|
|
|
|
Why commit time and not a commit count: a count is per-branch, so `dev` and
|
|
`main` count different histories of the same code and their versions end up
|
|
ordered by which branch accumulated more commits rather than by which is newer.
|
|
Commit time gives both branches the same number for the same source — which is
|
|
exactly what lets one AMO signature serve both channels (family rule 149, FC
|
|
issue #3092).
|
|
|
|
## Channels
|
|
|
|
`dev` and `main` each build and sign their own extension, and an install is
|
|
tied to whichever FC instance it points at — Firefox's static `update_url`
|
|
cannot apply here, since every FC install is a different host, so the extension
|
|
asks its configured backend. **The channel therefore IS the instance.**
|
|
Switching channel means repointing the FC URL in options and reinstalling from
|
|
that host; there is no separate channel setting, and adding one would
|
|
contradict each server build shipping its own extension.
|
|
|
|
The channel is reported *beside* the version, never inside it:
|
|
`/api/extension/manifest` answers `{"version": "...", "channel": "dev"}`. It is
|
|
optional — an instance that declares none simply omits the key, and the popup,
|
|
the toolbar tooltip and the Settings card all read exactly as they did before
|
|
the field existed. Do not be tempted to make it a `-dev` version suffix: the
|
|
comparator parses each dotted segment with `parseInt`, so a suffixed segment
|
|
reads as 0 and every dev build compares equal to every other, collapsing "no
|
|
update available" and "I cannot read this version" into one answer.
|
|
|
|
## Release
|
|
|
|
Nothing to do by hand. Push to `dev`: `build.yml` signs the extension if this
|
|
change moved the version, caches the signed XPI as a Forgejo `ext-<version>`
|
|
release, and bundles it into `fabledcurator:dev`. Merging to `main` derives the
|
|
same version, hits that cache, and bundles the byte-identical XPI into
|
|
`:latest` with no second AMO call.
|
|
|
|
AMO refuses to re-sign a version it has already issued, so signing is one-shot
|
|
per version — which is why the cache exists and why the version must never move
|
|
backwards.
|