The extension versions independently of @bquery/bquery: store review is slow
and unpredictable, and the bridge protocol — not the release train — is what
keeps the two compatible.
app.config.json is the single source of truth. bun run sync copies its
AppData.version into package.json and public/manifest.json, and every
deploy-* script runs sync first.
To cut a release:
# 1. bump AppData.version in app.config.json (semver, e.g. 1.1.0)
bun run sync
# 2. update CHANGELOG.md
git commit -am "chore: release 1.1.0"
git tag v1.1.0
git push --follow-tagsThe tag triggers .github/workflows/release.yml, which validates, builds both
targets, verifies and packages them, signs what can be signed (see
Signing), writes SHA256SUMS.txt and opens a draft GitHub
release with the artifacts attached. Review it, then publish.
Store manifests must use a numeric major.minor.patch[.build] version. Do not
put pre-release suffixes (1.1.0-beta.1) in AppData.version; use a build
segment (1.1.0.1) if you need one.
bun run deploy-v3 && bun run verify && bun run package # chromium-mv3
bun run deploy-v2 && bun run verify && bun run package # firefox-mv2Both zips land in artifacts/. bun run verify is not optional — it catches
a missing entry point, an unreplaced branding token, a content script that
accidentally became an ES module, and host permissions creeping back in.
Browser extensions are not signed the way a binary is — each store signs what it distributes, so what a release workflow can produce differs per target.
Every release attests its artifacts with
actions/attest-build-provenance,
which signs a provenance statement through Sigstore. No secrets are involved,
so it runs on every release. Anyone can check which workflow, commit and runner
produced a given file:
gh attestation verify bquery-devtools-1.0.0-chromium-mv3.zip \
--repo bQuery/devtools-extensionThis is the guarantee that actually matters for a store-distributed extension: it ties the zip you are about to upload to the commit it was built from.
AMO is the only party that can produce an installable, signed Firefox artifact
— signing there is a service, not a local key operation. When the repository
has AMO_JWT_ISSUER and AMO_JWT_SECRET
(API credentials)
configured as secrets, the workflow runs web-ext sign --channel unlisted and
attaches the resulting signed .xpi to the release.
Without those secrets the step is skipped, not failed: the release still
ships the unsigned MV2 zip for manual upload through the AMO dashboard. Use the
signed .xpi for self-distribution; a listed AMO release is signed by AMO on
upload either way.
There is nothing to sign locally. The Chrome Web Store re-signs every upload with its own key and assigns the extension id, so a self-signed CRX would be discarded. Self-hosted CRX distribution is a different (and much rarer) workflow that needs a private key this repository deliberately does not carry — if you need it, keep the key outside CI.
- Sign in to the Developer Dashboard with the account that owns the listing.
- Upload new package →
artifacts/bquery-devtools-<version>-chromium-mv3.zip. - Check the listing fields (they change less often than the code):
- Category: Developer Tools.
- Screenshots: the panel on a page running a bQuery app — component tree, signals, and timeline are the three that matter.
- Privacy: be precise here, because "collects nothing" is the wrong
answer. Chrome Web Store policy counts website content as user data,
and the panel reads plenty of it — the component tree, signal and store
values, and timeline entries from the inspected page. What the extension
does not do is send any of it anywhere: everything stays in the DevTools
process on the user's machine, there is no remote endpoint, and the only
thing written to
chrome.storage.localis the panel's own preferences (buffer size, poll interval, live-streaming toggle). Declare handling of website content, declare no transmission, and answer the certification questions accordingly — the extension neither sells data nor uses it for anything beyond the panel's displayed purpose.
- Justify the permissions. Reviewers ask about these two:
scripting— "injects a small relay into the inspected tab, only after the user explicitly enables live streaming and grants that site's origin";- the optional host permission — "requested at runtime for one origin at a time; the extension declares no host permissions and works without any".
- Submit. Review typically takes a few days; a permission change resets it.
Edge Add-ons accepts the same MV3 zip through Partner Center.
-
Build the MV2 target —
browser_specific_settings.gecko.idcomes fromAppData.firefox.geckoIdinapp.config.jsonand must stay stable across releases, or AMO treats the upload as a different add-on. -
Sign in at addons.mozilla.org/developers and upload
artifacts/bquery-devtools-<version>-firefox-mv2.zip. -
AMO requires reviewable sources for a bundled build. Provide the repository tag plus these build instructions:
bun install --frozen-lockfile bun run deploy-v2
Note the Bun version from
mise.tomlin the source-upload notes. -
Self-distribution (unlisted signing) uses the same zip via
web-ext signif you need a signed build outside AMO.
- Verify the published version loads in a clean profile in both browsers.
- Confirm the panel connects to a page running
connectDevtoolsBridge(), and that Enable live streaming still prompts for the origin permission — a store-side permission change can silently alter that prompt. - Move the release notes from Unreleased in
CHANGELOG.md.