From d2cd3db20f2e49d8950b5ce61208c7ef1d2522ee Mon Sep 17 00:00:00 2001 From: Nic Date: Mon, 20 Jul 2026 10:48:04 +0100 Subject: [PATCH] =?UTF-8?q?ci:=20release-artifacts=20workflow=20=E2=80=94?= =?UTF-8?q?=20Chrome=20zip,=20signed=20Firefox=20xpi,=20AMO=20source=20(V2?= =?UTF-8?q?-688)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a `v*` tag push (or manual workflow_dispatch), build from a clean checkout and produce the distributable artifacts, then attach them to the GitHub Release: - ant-webex-chrome-vX.Y.Z.zip — CWS upload package (manifest at zip root). - ant-webex-firefox-vX.Y.Z.xpi — signed, unlisted via `web-ext sign` when the AMO API secrets (WEB_EXT_API_KEY/SECRET) are set; installable by testers. Falls back to an unsigned .zip (with a warning) when creds are absent, so the rest of the release still succeeds. - ant-webex-source-vX.Y.Z.zip — `git archive` source tree for AMO's source-code submission requirement. Guards: version must agree across tag / manifest.json / package.json; web-ext lint runs before signing so a broken build doesn't burn an AMO version number. Runs in a `release` GitHub Environment so signing can be gated behind a required reviewer and secrets aren't exposed to fork-PR runs. RELEASING.md documents the tag flow + one-time AMO credential setup. Branched off main; independent of the i18n (#8) and CI-hardening (#9) PRs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 134 ++++++++++++++++++++++++++++++++++ RELEASING.md | 57 +++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 RELEASING.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ed66129 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,134 @@ +name: Release artifacts + +# Produces the store-ready, distributable artifacts from a clean checkout: +# • ant-webex-chrome-vX.Y.Z.zip — Chrome Web Store upload package +# • ant-webex-firefox-vX.Y.Z.xpi — signed, unlisted Firefox build (installable +# by testers) when AMO creds are configured; +# falls back to an unsigned .zip otherwise +# • ant-webex-source-vX.Y.Z.zip — source tree for AMO's source-code submission +# and attaches them to the GitHub Release for the tag. +# +# Trigger: push a version tag (e.g. `git tag v0.1.3 && git push origin v0.1.3`), +# or run manually from the Actions tab (workflow_dispatch). + +on: + push: + tags: ['v*'] + workflow_dispatch: + +permissions: + contents: write # create the GitHub Release + upload assets + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + package: + name: build · package · sign · release + runs-on: ubuntu-latest + # Scopes the AMO signing secrets and lets you optionally gate the run behind + # a required reviewer (Settings → Environments → release → protection rules). + environment: release + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + # Single source of truth for the version + a guard against drift: the tag + # (when tag-triggered) must equal manifest.json, which must equal package.json. + - name: Resolve and verify version + id: ver + run: | + MANIFEST="$(node -p "require('./src/manifest.json').version")" + PKG="$(node -p "require('./package.json').version")" + if [ "$MANIFEST" != "$PKG" ]; then + echo "::error::Version mismatch — src/manifest.json ($MANIFEST) != package.json ($PKG)" + exit 1 + fi + if [ "${{ github.event_name }}" = "push" ]; then + TAG="${GITHUB_REF_NAME#v}" + if [ "$TAG" != "$MANIFEST" ]; then + echo "::error::Tag v$TAG does not match the manifest/package version $MANIFEST — bump the version before tagging." + exit 1 + fi + fi + echo "version=$MANIFEST" >> "$GITHUB_OUTPUT" + echo "Releasing v$MANIFEST" + + - name: Build (Chrome + Firefox) + run: npm run build:all + + # Catch store-rejection issues before we spend an AMO version number signing. + - name: Lint Firefox build (web-ext) + run: npx --yes web-ext lint --source-dir=dist-firefox + + - name: Package Chrome zip + run: | + mkdir -p artifacts + (cd dist && zip -rX "../artifacts/ant-webex-chrome-v${{ steps.ver.outputs.version }}.zip" .) + + # AMO requires the source for minified/bundled add-ons. git archive gives a + # clean tree (tracked files only — no node_modules/dist) matching the tag. + - name: Package source zip (AMO source submission) + run: git archive --format=zip -o "artifacts/ant-webex-source-v${{ steps.ver.outputs.version }}.zip" HEAD + + # Signing needs AMO API creds. When present → signed, installable .xpi. + # When absent → an unsigned .zip so the rest of the release still succeeds. + - name: Detect AMO signing credentials + id: amo + env: + WEB_EXT_API_KEY: ${{ secrets.WEB_EXT_API_KEY }} + run: | + if [ -n "$WEB_EXT_API_KEY" ]; then + echo "present=true" >> "$GITHUB_OUTPUT" + else + echo "present=false" >> "$GITHUB_OUTPUT" + fi + + - name: Sign Firefox (unlisted → signed .xpi) + if: steps.amo.outputs.present == 'true' + env: + WEB_EXT_API_KEY: ${{ secrets.WEB_EXT_API_KEY }} + WEB_EXT_API_SECRET: ${{ secrets.WEB_EXT_API_SECRET }} + run: | + npx --yes web-ext sign \ + --source-dir=dist-firefox \ + --channel=unlisted \ + --api-key="$WEB_EXT_API_KEY" \ + --api-secret="$WEB_EXT_API_SECRET" \ + --artifacts-dir=artifacts + # web-ext names the signed file -.xpi; normalize it. + XPI="$(ls artifacts/*.xpi | head -1)" + mv "$XPI" "artifacts/ant-webex-firefox-v${{ steps.ver.outputs.version }}.xpi" + + - name: Package Firefox unsigned zip (fallback — no AMO creds) + if: steps.amo.outputs.present != 'true' + run: | + echo "::warning::AMO signing credentials (WEB_EXT_API_KEY / WEB_EXT_API_SECRET) are not set — producing an UNSIGNED Firefox zip, which will not install in release Firefox. Add the secrets to get a signed, installable .xpi. See RELEASING.md." + npx --yes web-ext build \ + --source-dir=dist-firefox \ + --filename="ant-webex-firefox-v${{ steps.ver.outputs.version }}-unsigned.zip" \ + --artifacts-dir=artifacts \ + --overwrite-dest + + - name: Upload workflow artifacts + uses: actions/upload-artifact@v4 + with: + name: ant-webex-v${{ steps.ver.outputs.version }} + path: artifacts/* + if-no-files-found: error + + # Only cut a GitHub Release on a real tag (not on manual/dispatch runs). + - name: Attach to GitHub Release + if: github.event_name == 'push' + uses: softprops/action-gh-release@v2 + with: + files: artifacts/* + fail_on_unmatched_files: true diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..fcca70a --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,57 @@ +# Releasing + +Release artifacts are built by CI ([`.github/workflows/release.yml`](./.github/workflows/release.yml)) +from a clean checkout — you don't hand-zip `dist/`. Cutting a release is just a +version bump and a tag. + +## Cut a release + +1. **Bump the version** in both `src/manifest.json` and `package.json` (they must + match — CI fails the release if they don't). Commit it. +2. **Tag and push:** + ```bash + git tag v0.1.3 # tag must equal the manifest/package version + git push origin main --tags + ``` + The tag push triggers the release workflow. +3. **Grab the artifacts.** They're attached to the GitHub Release for the tag + (and also downloadable from the workflow run under *Actions*): + - `ant-webex-chrome-vX.Y.Z.zip` — upload to the Chrome Web Store dashboard. + - `ant-webex-firefox-vX.Y.Z.xpi` — the **signed, unlisted** Firefox build; + testers install it directly (drag into Firefox). *Requires AMO creds — see + below; without them you get `…-unsigned.zip` instead, which won't install.* + - `ant-webex-source-vX.Y.Z.zip` — upload as the AMO **source-code submission**. + +You can also run it without tagging: **Actions → Release artifacts → Run +workflow** (`workflow_dispatch`). Manual runs upload the artifacts to the run but +don't create a GitHub Release. + +## One-time setup: AMO signing (for the Firefox `.xpi`) + +The signed Firefox build uses `web-ext sign` against the AMO API. Until these are +configured, the workflow still succeeds but emits an **unsigned** Firefox zip. + +1. **Get AMO API credentials** at + — an API **key** (issuer, `user:…`) and **secret**. +2. **Store them as GitHub secrets** named `WEB_EXT_API_KEY` and + `WEB_EXT_API_SECRET`. Recommended: put them on a protected **Environment** + (Settings → Environments → `release`) rather than plain repo secrets — the + workflow already runs in the `release` environment, so you can add a + **required reviewer** to gate every release, and environment secrets are never + exposed to fork-PR runs. +3. **Add-on ID.** The Firefox manifest already declares + `browser_specific_settings.gecko.id = autonomi@webex`. Unlisted signing + auto-registers the add-on under that ID on the first run — no manual AMO + listing needed. + +### Notes + +- **Versions are one-shot on AMO.** It won't re-sign a version it has already + seen, so every signed build needs a fresh `manifest.json` version. Bump before + re-tagging. +- **Unlisted ≠ listed.** This produces a self-distributed build for testers, not + a public AMO listing. Publishing to the public Chrome Web Store / AMO listings + is tracked separately (see the store-upload automation ticket). +- **Reproducibility.** The build is deterministic (verified by CI's + reproducible-build check), so the artifact equals the tagged commit built with + the pinned toolchain — matching `BUILD.md`'s AMO source-reproduction steps.