`packaging.sh pathspec` excluded `extension/scripts/**` — the same list
web-ext ignores. But the two lists answer different questions, and this is
the one place they disagree.
packaging.sh is not packaged into the XPI. It does decide the version
string, and build.yml stamps that string into the manifest.json that IS
packaged. Changing how the version is computed therefore changes the shipped
bytes, and the derivation has to see it.
Harmless while every push rebuilt the web image. Step 4 made the rebuild
conditional on the derived revision moving, which turns it into a silent
failure: a packaging.sh change yields a NEW version, so sign-extension misses
its ext-<version> cache and signs — while build-web sees an unmoved revision,
reuses the published image and ships the OLD XPI. One orphaned AMO signature,
and an instance serving code the registry calls current. Found while checking
the ground under step 5, which changes the version format and is exactly the
commit that would have hit it.
Split the list rather than widening the shared one: NOT_VERSION_RELEVANT
drives the pathspec, NOT_PACKAGED_TRACKED still drives web-ext's ignore
list, and scripts/ stays out of the XPI. The two directions are not
symmetric, which is why the version list is the narrower one — too wide
costs a re-sign and a rebuild for a change that ships nothing new, too
narrow serves stale bytes and says nothing.
No version churn: the last packaging.sh commit predates the current
extension revision, so the derived version is unchanged at 1.0.3500147 and
web's revision stays a7e626a67a.
Both suites now pin the disagreement from their own side, because the
tempting fix for either half is to make the lists one again — version.spec.js
asserts the pathspec does NOT exclude scripts while the ignore list still
does, and test_artifact_paths.py asserts packaging.sh is inside the extension
and web path sets.
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
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 lintpassesnpm run startloads 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/, click floating button → toast
- Subscriptions list: popup → "Sources" tab → list renders
- Check now: click play icon on source row → no error toast
Versioning — don't hand-edit the patch number
The shipped version is derived, not committed. scripts/packaging.sh version returns MAJOR.MINOR from manifest.json plus a patch component
that is the commit time of the newest change to a packaged extension file,
in minutes since 2020-01-01. 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 patch number does nothing. It is overwritten before web-ext ever reads it. There is no bump to make, and none to forget.
- MAJOR.MINOR is still yours. It carries the deliberate meaning, it is read
from
manifest.jsonalone, and CI fails theextension-versionlane if the two files disagree on it. npm run buildlocally produces an XPI labelled with the committed version, since nothing stamped it. Fine for loading into a test profile; not what ships.
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.