ext: MV3 manifest + esbuild build; drop polyfill for Firefox's browser global

Firefox-only extension, so webextension-polyfill (a Chrome shim) is dead
weight and forces a bundler just to resolve one bare import. Use the native
`browser.*` global with @types/firefox-webext-browser instead.

  - manifest.json: MV3, event-page background (dist/background.js), the
    docs/05 §7 permission set (<all_urls> in host_permissions),
    strict_min_version 128.0, data_collection_permissions none.
  - scripts/build.mjs: esbuild bundle of src/background/index.ts -> dist/,
    esm, target firefox128. Wired to `prepare` so `npm ci` produces the
    bundle and CI's `web-ext lint` (which needs it to exist) passes with no
    added CI step. dist/ stays gitignored.
  - src/background/index.ts: event-page entry — brings the transport up,
    holds the shared reference. Capture surfaces attach here next.
  - transport/storage.ts, transport/index.ts: use the browser global.
  - tests/setup.ts: stub the browser global instead of mocking a module.

web-ext lint clean (0/0/0). typecheck clean. 38 tests still green.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
This commit is contained in:
2026-09-10 13:50:23 +04:00
co-authored by Claude Sonnet 5
parent 90e2580b05
commit c5d596d93b
10 changed files with 973 additions and 68 deletions
+46
View File
@@ -0,0 +1,46 @@
// Bundles the extension's TypeScript entry points into dist/ for Firefox to load.
//
// Runs on `npm run build` and, via the `prepare` script, on every `npm ci` — so CI's
// `web-ext lint` (which needs the referenced bundles to exist) works without a separate
// build step. Firefox-only: no polyfill, native ESM, `browser.*` is a global.
import { build } from 'esbuild';
import { rm, mkdir } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const outdir = resolve(root, 'dist');
// One entry per manifest surface. Add popup/options/content here as they land.
const entryPoints = {
background: resolve(root, 'src/background/index.ts'),
};
await rm(outdir, { recursive: true, force: true });
await mkdir(outdir, { recursive: true });
const watch = process.argv.includes('--watch');
const dev = watch || process.argv.includes('--dev');
const options = {
entryPoints,
outdir,
bundle: true,
format: 'esm',
target: ['firefox128'],
platform: 'browser',
sourcemap: dev ? 'inline' : 'linked',
minify: !dev,
logLevel: 'info',
// A bare `import ... from 'ws'` etc. must never reach a bundle — fail loud if one does.
external: [],
};
if (watch) {
const ctx = await (await import('esbuild')).context(options);
await ctx.watch();
console.log('esbuild: watching');
} else {
await build(options);
}