From 8f5438fffd484d6906b0f1ddc6550287d6759eb0 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Sun, 12 Jul 2026 22:25:16 +0800 Subject: [PATCH] docs: plan GitHub Firefox update releases --- .../2026-07-12-github-firefox-updates.md | 329 ++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-12-github-firefox-updates.md diff --git a/docs/superpowers/plans/2026-07-12-github-firefox-updates.md b/docs/superpowers/plans/2026-07-12-github-firefox-updates.md new file mode 100644 index 0000000..96a1bbb --- /dev/null +++ b/docs/superpowers/plans/2026-07-12-github-firefox-updates.md @@ -0,0 +1,329 @@ +# GitHub Firefox Updates Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Publish signed Firefox XPI releases to GitHub Releases and let installed alpha extensions discover them through a GitHub-hosted update manifest. + +**Architecture:** Firefox keeps one immutable `update_url` targeting the current GitHub Release's `updates.json` asset. A small Node helper owns the tag, asset URL and update-manifest formatting so it has focused unit tests. The existing local signing command remains local; an explicit publishing command uploads its two artifacts to the public GitHub release. + +**Tech Stack:** Manifest V2 JSON, Node.js CommonJS tests, Bash, `web-ext`, GitHub CLI (`gh`). + +## Global Constraints + +- Publish repository: `mirivlad/verstak-browser-extension`. +- Firefox addon ID: `verstak-bridge@verstak.app`. +- Initial GitHub release: `v2.0.2`; package and both browser manifests are version `2.0.2`. +- Every alpha GitHub release is ordinary/latest, not a prerelease. +- XPI remains Mozilla-signed as an unlisted/self-distributed addon before upload. +- `npm run release:firefox` remains a local sign-and-package action. +- `npm run publish:firefox` is the only command allowed to create or modify a GitHub Release. +- Generated `release/` output remains untracked. + +--- + +### Task 1: Make GitHub update metadata testable + +**Files:** +- Create: `scripts/firefox-github-release.js` +- Create: `scripts/test-firefox-github-release.js` +- Modify: `package.json:8-15` + +**Interfaces:** +- Produces `releaseTag(version)`, `releaseAssetURL(version, assetName)`, and `updateManifest(addonID, version, assetName)` from `scripts/firefox-github-release.js`. +- `updateManifest` returns the object written as `updates.json` and uses the fixed GitHub repository. +- Later shell scripts invoke `node scripts/firefox-github-release.js write-updates `. + +- [ ] **Step 1: Write the failing metadata test** + +Create `scripts/test-firefox-github-release.js` with: + +```js +const assert = require('assert'); +const release = require('./firefox-github-release'); + +const version = '2.0.2'; +const asset = 'verstak-firefox-2.0.2.xpi'; +assert.equal(release.releaseTag(version), 'v2.0.2'); +assert.equal( + release.releaseAssetURL(version, asset), + 'https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.2/verstak-firefox-2.0.2.xpi', +); +assert.deepEqual(release.updateManifest('verstak-bridge@verstak.app', version, asset), { + addons: { + 'verstak-bridge@verstak.app': { + updates: [{ + version: '2.0.2', + update_link: 'https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.2/verstak-firefox-2.0.2.xpi', + }], + }, + }, +}); +console.log('Firefox GitHub release metadata tests passed'); +``` + +- [ ] **Step 2: Run the focused test and verify it fails** + +Run: `node scripts/test-firefox-github-release.js` + +Expected: failure because `scripts/firefox-github-release.js` does not yet exist. + +- [ ] **Step 3: Implement the metadata helper** + +Create `scripts/firefox-github-release.js` with these exact exports and CLI: + +```js +const fs = require('fs'); +const REPOSITORY = 'mirivlad/verstak-browser-extension'; + +function releaseTag(version) { return `v${version}`; } +function releaseAssetURL(version, assetName) { + return `https://github.com/${REPOSITORY}/releases/download/${releaseTag(version)}/${assetName}`; +} +function updateManifest(addonID, version, assetName) { + return { addons: { [addonID]: { updates: [{ version, update_link: releaseAssetURL(version, assetName) }] } } }; +} + +if (require.main === module) { + const [, , command, addonID, version, assetName, outputPath] = process.argv; + if (command !== 'write-updates' || !addonID || !version || !assetName || !outputPath) process.exitCode = 2; + else fs.writeFileSync(outputPath, `${JSON.stringify(updateManifest(addonID, version, assetName), null, 2)}\n`); +} + +module.exports = { REPOSITORY, releaseTag, releaseAssetURL, updateManifest }; +``` + +- [ ] **Step 4: Register and run the test** + +Append `node scripts/test-firefox-github-release.js` to the existing `test` +script in `package.json`, then run: + +```bash +npm test +``` + +Expected: all existing tests and `Firefox GitHub release metadata tests passed`. + +- [ ] **Step 5: Commit and push** + +```bash +git add package.json scripts/firefox-github-release.js scripts/test-firefox-github-release.js +git commit -m "test: cover Firefox GitHub release metadata" +git push origin main +``` + +### Task 2: Point Firefox and local release artifacts at GitHub + +**Files:** +- Modify: `firefox/manifest.json:8-17` +- Modify: `scripts/release-firefox-xpi.sh:29-70` +- Test: `scripts/test-firefox-github-release.js` + +**Interfaces:** +- Firefox reads `https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json`. +- `release-firefox-xpi.sh` invokes the Task 1 helper to write a versioned update manifest. +- It continues producing `release/firefox/verstak-firefox-.xpi` and `release/firefox/updates.json` locally. + +- [ ] **Step 1: Extend the failing test with the stable manifest URL** + +Add this assertion to `scripts/test-firefox-github-release.js` before its final +success message: + +```js +const firefoxManifest = require('../firefox/manifest.json'); +assert.equal( + firefoxManifest.browser_specific_settings.gecko.update_url, + 'https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json', +); +``` + +- [ ] **Step 2: Run the focused test and verify it fails** + +Run: `node scripts/test-firefox-github-release.js` + +Expected: assertion failure because the manifest still uses `mirv.top`. + +- [ ] **Step 3: Replace the update endpoint and JSON writer** + +In `firefox/manifest.json`, replace only the Gecko `update_url` value with: + +```json +"update_url": "https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json" +``` + +In `scripts/release-firefox-xpi.sh`, remove `UPDATE_BASE_URL` and replace the +heredoc that writes `updates.json` with: + +```bash +node scripts/firefox-github-release.js write-updates \ + "$ADDON_ID" "$VERSION" "$RELEASE_XPI" "$RELEASE_DIR/updates.json" +``` + +- [ ] **Step 4: Run targeted checks** + +Run: + +```bash +node scripts/test-firefox-github-release.js +bash -n scripts/release-firefox-xpi.sh +npm test +npm run build +``` + +Expected: all commands succeed; `dist/firefox/manifest.json` contains the +GitHub update URL after the build. + +- [ ] **Step 5: Commit and push** + +```bash +git add firefox/manifest.json scripts/release-firefox-xpi.sh scripts/test-firefox-github-release.js +git commit -m "feat: host Firefox updates on GitHub Releases" +git push origin main +``` + +### Task 3: Add explicit GitHub publishing and public instructions + +**Files:** +- Create: `scripts/publish-firefox-github-release.sh` +- Modify: `package.json:8-15` +- Modify: `README.md:42-68` +- Test: `scripts/publish-firefox-github-release.sh` + +**Interfaces:** +- `npm run publish:firefox` calls `scripts/publish-firefox-github-release.sh`. +- The script runs the existing local release command, then uploads + `verstak-firefox-.xpi` and `updates.json` to release tag + `v` in `mirivlad/verstak-browser-extension`. +- A pre-existing tag receives asset replacement through `gh release upload --clobber`. + +- [ ] **Step 1: Write the shell-contract test** + +Create a Node test that reads the publish script and asserts the mandatory +commands are present: + +```js +const assert = require('assert'); +const fs = require('fs'); +const source = fs.readFileSync('scripts/publish-firefox-github-release.sh', 'utf8'); +assert.match(source, /gh auth status/); +assert.match(source, /gh release create/); +assert.match(source, /gh release upload/); +assert.match(source, /--clobber/); +assert.match(source, /--latest/); +``` + +Place those assertions in `scripts/test-firefox-github-release.js` and run the +test. Expected: it fails before the publish script exists. + +- [ ] **Step 2: Implement an explicit idempotent publisher** + +Create `scripts/publish-firefox-github-release.sh` with this behavior: + +1. Set `ROOT_DIR`, `cd` to it and run `gh auth status`. +2. Run `./scripts/release-firefox-xpi.sh`. +3. Read `VERSION` from `dist/firefox/manifest.json`, set `TAG="v${VERSION}"`, + `REPOSITORY="mirivlad/verstak-browser-extension"`, and locate the two local + release assets by exact names. +4. Run `gh release view "$TAG" --repo "$REPOSITORY"`; when it is absent, run + `gh release create "$TAG" "$XPI" "$UPDATES" --repo "$REPOSITORY" --title + "Verstak Browser Extension $VERSION" --generate-notes --latest --target + "$(git rev-parse HEAD)"`. +5. When the release already exists, run `gh release upload "$TAG" "$XPI" + "$UPDATES" --repo "$REPOSITORY" --clobber`. +6. Print the GitHub release URL with `gh release view "$TAG" --repo + "$REPOSITORY" --json url --jq .url`. + +- [ ] **Step 3: Register the command and document it** + +Add to `package.json`: + +```json +"publish:firefox": "./scripts/publish-firefox-github-release.sh" +``` + +Rewrite the Firefox Release README section so it documents: + +- the public [GitHub Releases](https://github.com/mirivlad/verstak-browser-extension/releases) page; +- `VERSTAK_BROWSER_ENV=/path/to/.env npm run publish:firefox`; +- that signing remains unlisted with Mozilla but distribution and automatic + updates use GitHub Release assets; +- that the first `v2.0.2` publish bootstraps the `latest/download/updates.json` + endpoint. + +- [ ] **Step 4: Run checks without publishing** + +Run: + +```bash +bash -n scripts/publish-firefox-github-release.sh +node scripts/test-firefox-github-release.js +npm test +npm run build +git diff --check +``` + +Expected: all commands succeed. Do not invoke `npm run publish:firefox` in this +task; it creates a public GitHub Release and needs available AMO credentials. + +- [ ] **Step 5: Commit and push** + +```bash +git add README.md package.json scripts/publish-firefox-github-release.sh scripts/test-firefox-github-release.js +git commit -m "feat: publish Firefox releases to GitHub" +git push origin main +``` + +### Task 4: Bootstrap and verify the first public update release + +**Files:** +- Generated: `release/firefox/verstak-firefox-2.0.2.xpi` (untracked) +- Generated: `release/firefox/updates.json` (untracked) + +**Interfaces:** +- Consumes the Task 3 `npm run publish:firefox` command and valid AMO signing credentials. +- Produces GitHub Release tag `v2.0.2` and public XPI/update-manifest assets. + +- [ ] **Step 1: Verify release credentials before write operations** + +Run: + +```bash +gh auth status +test -n "${VERSTAK_BROWSER_ENV:-}" || test -f .env +``` + +Expected: authenticated GitHub CLI and a configured environment file path for +the existing AMO signing command. + +- [ ] **Step 2: Publish the first release** + +Run: + +```bash +npm run publish:firefox +``` + +Expected: Mozilla signing succeeds, GitHub Release `v2.0.2` is latest, and it +contains `verstak-firefox-2.0.2.xpi` plus `updates.json`. + +- [ ] **Step 3: Verify public delivery** + +Run: + +```bash +gh release view v2.0.2 --repo mirivlad/verstak-browser-extension --json isLatest,url,assets +curl -fsSL https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json +``` + +Expected: the release is latest and the JSON contains addon ID +`verstak-bridge@verstak.app`, version `2.0.2`, and the versioned GitHub XPI +asset URL. + +- [ ] **Step 4: Record release verification without committing assets** + +Run: + +```bash +git status --short +``` + +Expected: no generated `release/` assets are staged or committed.