name: extension # Lint + unit tests. The sign-and-publish dance moved into build.yml's # `sign-extension` job (2026-05-25) — `:latest` now always bundles the XPI # because sign-extension runs as a build-web dependency in the SAME workflow, # eliminating the prior race between build.yml and a separate extension.yml. # Signed XPIs are cached in Forgejo Release Assets named `ext-`. on: push: branches: [dev, main] paths: - 'extension/**' - '.forgejo/workflows/extension.yml' # test/version.spec.js asserts ci.yml's extension-version guard never # ignores a file web-ext actually packages, so a ci.yml-only edit can # break this suite and must trigger it. - '.forgejo/workflows/ci.yml' pull_request: branches: [main] paths: - 'extension/**' - '.forgejo/workflows/ci.yml' workflow_dispatch: jobs: lint: runs-on: python-ci container: image: node:24-bookworm-slim steps: - uses: actions/checkout@v4 # Not --no-save: vitest and web-ext are both real devDependencies now, # and the suite needs vitest resolvable from node_modules. - name: Install dev dependencies run: cd extension && npm install --no-audit --no-fund - name: Lint run: cd extension && npm run lint # Pure-logic specs over lib/url.js and lib/platforms.js plus manifest / # package version-consistency checks. No browser, no network. - name: Unit tests run: cd extension && npm run test:unit # Everything else about packaging is asserted against our own declaration # of what ships. This is the only check that asks web-ext what it ACTUALLY # put in the archive. Until now that was an unverified assumption about # glob semantics — and a fragile one: `test/**` reaches web-ext intact # only because callers `set -f` first, so losing that quoting would # silently start shipping dev files with no other signal. - name: Verify XPI contents run: | set -eu command -v unzip >/dev/null 2>&1 || { apt-get update -qq && apt-get install -y -qq unzip; } cd extension npm run build ZIP=$(ls web-ext-artifacts/*.zip | head -1) echo "=== packaged entries in $ZIP ===" unzip -Z1 "$ZIP" | sort echo "=== end ===" ENTRIES=$(unzip -Z1 "$ZIP") fail=0 # Must NOT ship: repo infrastructure with no business in a user's browser. for pat in 'test/' 'scripts/' 'vitest.config.js' 'package.json' 'package-lock.json' 'README.md' 'node_modules/' 'web-ext-artifacts/'; do if echo "$ENTRIES" | grep -q "^$pat"; then echo "ERROR: '$pat' was packaged into the XPI but must not be" fail=1 fi done # Must ship: if an exclusion pattern ever over-matches, the extension # breaks at runtime rather than at build time, so assert presence too. for req in 'manifest.json' 'lib/url.js' 'lib/api.js' 'lib/platforms.js' 'lib/cookies.js'; do if ! echo "$ENTRIES" | grep -q "^$req$"; then echo "ERROR: '$req' is missing from the XPI" fail=1 fi done for dir in 'background/' 'popup/' 'options/' 'content/' 'icons/'; do if ! echo "$ENTRIES" | grep -q "^$dir"; then echo "ERROR: nothing from '$dir' was packaged" fail=1 fi done [ "$fail" -eq 0 ] || exit 1 echo "XPI contents verified."