Skip to content

build(extension): add TARGET-driven dual builds with Firefox manifest - #2000

Open
ManthanNimodiya wants to merge 5 commits into
CapSoftware:mainfrom
ManthanNimodiya:feat/extension-firefox-build
Open

build(extension): add TARGET-driven dual builds with Firefox manifest#2000
ManthanNimodiya wants to merge 5 commits into
CapSoftware:mainfrom
ManthanNimodiya:feat/extension-firefox-build

Conversation

@ManthanNimodiya

@ManthanNimodiya ManthanNimodiya commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Stacked on #1998, only the last commit(s) are new here, the earlier ones will disappear once the base PR merges.

TARGET=chrome|firefox vite builds → dist/chrome/dist/firefox.
Manifests move to manifests/manifest.{chrome,firefox}.json (vite copies the right one per target; a unit test keeps them in lockstep).

Firefox manifest: event-page background, options_ui, no offscreen/tabCapture, browser_specific_settings.gecko (id, min 128). New scripts: build:firefox, dev:firefox, run:firefox/lint:firefox (web-ext). e2e stays Chromium-only against dist/chrome. web-ext lint: 0 errors.

Greptile Summary

This PR adds target-specific Chrome and Firefox extension builds. The main changes are:

  • Chrome and Firefox build scripts with separate dist/chrome and dist/firefox outputs.
  • Target-specific manifests copied into each build output.
  • Firefox manifest support with an event-page background and Gecko settings.
  • Recorder host and recorder page renames for shared Chrome and Firefox packaging.
  • Chromium e2e tests updated to load the Chrome build output.

Confidence Score: 5/5

This looks safe to merge from this follow-up review.

  • No new blocking issue was found beyond the already reported review threads.
  • The latest Chrome build and package changes narrow the default output to the Chrome target.

Important Files Changed

Filename Overview
.github/workflows/publish-chrome-extension.yml Updates the Chrome release package step to zip the Chrome build output.
apps/chrome-extension/package.json Adds target-specific build, dev, run, and lint scripts for Chrome and Firefox.
apps/chrome-extension/manifests/manifest.chrome.json Moves the Chrome manifest into the new target-specific manifests directory.
apps/chrome-extension/manifests/manifest.firefox.json Adds the Firefox manifest with Gecko settings and Firefox-specific background configuration.
apps/chrome-extension/src/background/recorder-host.ts Adds a shared recorder host helper for the background script.
apps/chrome-extension/src/background/service-worker.ts Routes recorder host lifecycle calls through the new helper and uses the runtime extension protocol.
apps/chrome-extension/vite.config.ts Adds target-aware output paths and manifest copying for the main extension build.
apps/chrome-extension/vite.shared.ts Centralizes target resolution, output directory selection, and build-time target injection.

Reviews (2): Last reviewed commit: "fix(extension): default build produces o..." | Re-trigger Greptile

Context used:

  • Context used - CLAUDE.md (source)
  • Context used - AGENTS.md (source)

Comment thread apps/chrome-extension/package.json Outdated
"scripts": {
"build": "rm -rf dist && vite build && vite build --config vite.content.config.ts && vite build --config vite.content-overlay.config.ts",
"dev": "rm -rf dist && (trap 'kill 0' INT TERM; vite build --watch --config vite.content.config.ts --mode development & vite build --watch --config vite.content-overlay.config.ts --mode development & vite build --watch --mode development & wait)",
"build": "pnpm build:chrome && pnpm build:firefox",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Chrome Package Contains Both Targets

When the Chrome publish workflow runs the default build, this script now creates both dist/chrome and dist/firefox. The existing package step zips dist as the extension root, so the upload contains target subdirectories and Firefox artifacts instead of a valid Chrome extension layout.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/chrome-extension/package.json
Line: 7

Comment:
**Chrome Package Contains Both Targets**

When the Chrome publish workflow runs the default `build`, this script now creates both `dist/chrome` and `dist/firefox`. The existing package step zips `dist` as the extension root, so the upload contains target subdirectories and Firefox artifacts instead of a valid Chrome extension layout.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +9 to +16
const getRecorderContexts = async () => {
const recorderUrl = chrome.runtime.getURL(RECORDER_URL);
return new Promise<Array<{ documentUrl?: string }>>((resolve) => {
chrome.runtime.getContexts(
{
contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT],
documentUrls: [recorderUrl],
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Firefox Reaches Offscreen APIs

The shared background script imports this helper for both targets, but hasRecorderHost() and ensureRecorderHost() call Chrome-only offscreen APIs without checking the target. In the Firefox build, normal paths like starting a recording, probing the mic, enumerating devices, or connecting camera preview can reach chrome.runtime.getContexts or chrome.offscreen.createDocument, producing a runtime error instead of opening a Firefox recorder page.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/chrome-extension/src/background/recorder-host.ts
Line: 9-16

Comment:
**Firefox Reaches Offscreen APIs**

The shared background script imports this helper for both targets, but `hasRecorderHost()` and `ensureRecorderHost()` call Chrome-only offscreen APIs without checking the target. In the Firefox build, normal paths like starting a recording, probing the mic, enumerating devices, or connecting camera preview can reach `chrome.runtime.getContexts` or `chrome.offscreen.createDocument`, producing a runtime error instead of opening a Firefox recorder page.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +1 to +46
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";

// The two manifests are maintained by hand; these assertions keep the parts
// that must not drift (version, entry points, content scripts, resources) in
// lockstep and pin the deliberate per-browser differences.

type Manifest = {
manifest_version: number;
name: string;
short_name: string;
version: string;
homepage_url: string;
icons: Record<string, string>;
action: unknown;
background: {
service_worker?: string;
scripts?: string[];
type?: string;
};
permissions: string[];
host_permissions: string[];
content_scripts: unknown[];
web_accessible_resources: Array<{
resources: string[];
matches: string[];
use_dynamic_url?: boolean;
}>;
options_page?: string;
options_ui?: { page: string };
browser_specific_settings?: {
gecko?: {
id?: string;
strict_min_version?: string;
};
};
};

const loadManifest = (target: "chrome" | "firefox"): Manifest =>
JSON.parse(
readFileSync(
resolve(__dirname, `../../manifests/manifest.${target}.json`),
"utf8",
),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apps/chrome-extension is type: module, so __dirname won’t exist if Vitest executes this test as ESM. Safer to derive it from import.meta.url.

Suggested change
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
// The two manifests are maintained by hand; these assertions keep the parts
// that must not drift (version, entry points, content scripts, resources) in
// lockstep and pin the deliberate per-browser differences.
type Manifest = {
manifest_version: number;
name: string;
short_name: string;
version: string;
homepage_url: string;
icons: Record<string, string>;
action: unknown;
background: {
service_worker?: string;
scripts?: string[];
type?: string;
};
permissions: string[];
host_permissions: string[];
content_scripts: unknown[];
web_accessible_resources: Array<{
resources: string[];
matches: string[];
use_dynamic_url?: boolean;
}>;
options_page?: string;
options_ui?: { page: string };
browser_specific_settings?: {
gecko?: {
id?: string;
strict_min_version?: string;
};
};
};
const loadManifest = (target: "chrome" | "firefox"): Manifest =>
JSON.parse(
readFileSync(
resolve(__dirname, `../../manifests/manifest.${target}.json`),
"utf8",
),
);
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
// The two manifests are maintained by hand; these assertions keep the parts
// that must not drift (version, entry points, content scripts, resources) in
// lockstep and pin the deliberate per-browser differences.
const __dirname = dirname(fileURLToPath(import.meta.url));
type Manifest = {
manifest_version: number;
name: string;
short_name: string;
version: string;
homepage_url: string;
icons: Record<string, string>;
action: unknown;
background: {
service_worker?: string;
scripts?: string[];
type?: string;
};
permissions: string[];
host_permissions: string[];
content_scripts: unknown[];
web_accessible_resources: Array<{
resources: string[];
matches: string[];
use_dynamic_url?: boolean;
}>;
options_page?: string;
options_ui?: { page: string };
browser_specific_settings?: {
gecko?: {
id?: string;
strict_min_version?: string;
};
};
};
const loadManifest = (target: "chrome" | "firefox"): Manifest =>
JSON.parse(
readFileSync(
resolve(__dirname, `../../manifests/manifest.${target}.json`),
"utf8",
),
);

const getRecorderContexts = async () => {
const recorderUrl = chrome.runtime.getURL(RECORDER_URL);
return new Promise<Array<{ documentUrl?: string }>>((resolve) => {
chrome.runtime.getContexts(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file assumes chrome.runtime.getContexts + chrome.offscreen.createDocument exist. Since this PR adds a Firefox build (and the manifest drops the offscreen permission), it might be worth adding a Firefox-specific implementation here (or at least a feature-detect + clearer error) so sendOffscreen(...) doesn’t end up throwing a TypeError at runtime.

@socket-security

socket-security Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​web-ext@​10.5.0961001009370

View full report

@socket-security

socket-security Bot commented Jul 21, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @fregante/relaxed-json is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/web-ext@10.5.0npm/@fregante/relaxed-json@2.0.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@fregante/relaxed-json@2.0.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @pnpm/network.ca-file is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/web-ext@10.5.0npm/@pnpm/network.ca-file@1.0.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@pnpm/network.ca-file@1.0.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@ManthanNimodiya

Copy link
Copy Markdown
Contributor Author

@greptileai

@ManthanNimodiya
ManthanNimodiya force-pushed the feat/extension-firefox-build branch from 3458174 to 4077348 Compare August 6, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant