Add browser abstraction layer (compat.ts) that normalizes Chrome/Firefox/ Edge/Safari extension APIs behind a unified promise-based interface. Replace all direct chrome.* calls with compat layer across service worker, sync engine, badge, storage, popup, and options modules. - Browser-specific manifests in manifests/ (Firefox MV3 with gecko settings, Edge/Safari variants) - Multi-target build system: `npm run build` produces all four browser builds in build/<browser>/ - Per-browser build scripts: build:chrome, build:firefox, build:edge, build:safari - Auto-detects browser at runtime for platform-specific device registration - All 38 existing tests pass, typecheck clean Co-Authored-By: Paperclip <noreply@paperclip.ing>
102 lines
2.7 KiB
JavaScript
102 lines
2.7 KiB
JavaScript
import * as esbuild from "esbuild";
|
|
import { cpSync, mkdirSync, existsSync } from "fs";
|
|
import { join, dirname } from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
const isWatch = process.argv.includes("--watch");
|
|
const targetBrowser = process.argv.find((a) => a.startsWith("--browser="))?.split("=")[1] || "all";
|
|
|
|
const browsers = targetBrowser === "all"
|
|
? ["chrome", "firefox", "edge", "safari"]
|
|
: [targetBrowser];
|
|
|
|
// Browser-specific esbuild targets
|
|
const browserTargets = {
|
|
chrome: "chrome120",
|
|
edge: "chrome120", // Edge is Chromium-based
|
|
firefox: "firefox109",
|
|
safari: "safari16",
|
|
};
|
|
|
|
const sharedBuildOptions = {
|
|
entryPoints: [
|
|
"src/background/service-worker.ts",
|
|
"src/popup/popup.ts",
|
|
"src/options/options.ts",
|
|
],
|
|
bundle: true,
|
|
format: "esm",
|
|
sourcemap: true,
|
|
minify: !isWatch,
|
|
// Force CJS resolution for libsodium (ESM entry has broken sibling import)
|
|
alias: {
|
|
"libsodium-wrappers-sumo":
|
|
"./node_modules/libsodium-wrappers-sumo/dist/modules-sumo/libsodium-wrappers.js",
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Copy static assets (HTML, icons, manifest) to a browser-specific build directory.
|
|
*/
|
|
function copyStaticAssets(browser) {
|
|
const outDir = join(__dirname, "build", browser);
|
|
mkdirSync(outDir, { recursive: true });
|
|
|
|
// Copy manifest
|
|
const manifestSrc = join(__dirname, "manifests", `${browser}.json`);
|
|
if (existsSync(manifestSrc)) {
|
|
cpSync(manifestSrc, join(outDir, "manifest.json"));
|
|
}
|
|
|
|
// Copy src (HTML, icons)
|
|
const srcDir = join(__dirname, "src");
|
|
cpSync(srcDir, join(outDir, "src"), { recursive: true, filter: (src) => !src.endsWith(".ts") });
|
|
}
|
|
|
|
async function buildBrowser(browser) {
|
|
const outDir = join("build", browser, "dist");
|
|
|
|
const options = {
|
|
...sharedBuildOptions,
|
|
outdir: outDir,
|
|
target: browserTargets[browser] || "es2020",
|
|
};
|
|
|
|
if (isWatch && browsers.length === 1) {
|
|
const ctx = await esbuild.context(options);
|
|
await ctx.watch();
|
|
console.log(`Watching for changes (${browser})...`);
|
|
} else {
|
|
await esbuild.build(options);
|
|
}
|
|
|
|
copyStaticAssets(browser);
|
|
console.log(`Build complete: ${browser} → build/${browser}/`);
|
|
}
|
|
|
|
// Also build the default dist/ for backwards compatibility (Chrome)
|
|
async function buildDefault() {
|
|
const options = {
|
|
...sharedBuildOptions,
|
|
outdir: "dist",
|
|
target: "chrome120",
|
|
};
|
|
|
|
if (isWatch) {
|
|
const ctx = await esbuild.context(options);
|
|
await ctx.watch();
|
|
console.log("Watching for changes (default)...");
|
|
} else {
|
|
await esbuild.build(options);
|
|
console.log("Build complete (default dist/).");
|
|
}
|
|
}
|
|
|
|
// Build all targets
|
|
await buildDefault();
|
|
for (const browser of browsers) {
|
|
await buildBrowser(browser);
|
|
}
|