From 9c91dea5be9237a8522e9f0c77349784df0f704a Mon Sep 17 00:00:00 2001 From: Pavel Ivanov Date: Sat, 7 Dec 2024 00:40:45 +0200 Subject: [PATCH] feat: Brave, Chrome and Firefox, add browser extension build automation --- .github/workflows/build-extension.yml | 66 +++ .gitignore | 4 + CHROME_EXTENSION_GUIDE.md | 23 +- packages/extension/.env.example | 20 +- packages/extension/README.md | 72 ++- packages/extension/package.json | 18 +- .../extension/public/icon/logo-animated.svg | 23 + packages/extension/src/background/index.ts | 186 +++++-- .../extension/src/background/update-badge.ts | 48 +- packages/extension/src/content/index.ts | 95 ++-- .../src/{utils => inject}/dev-tools-hook.ts | 13 +- packages/extension/src/inject/index.ts | 71 ++- packages/extension/src/manifest.json | 51 +- packages/extension/src/types/global.d.ts | 45 +- packages/extension/src/types/messages.ts | 39 +- packages/extension/src/utils/constants.ts | 2 + packages/extension/src/utils/helpers.ts | 13 + packages/extension/vite.config.ts | 120 +++-- pnpm-lock.yaml | 496 +++++++++++++++--- 19 files changed, 1088 insertions(+), 317 deletions(-) create mode 100644 .github/workflows/build-extension.yml create mode 100644 packages/extension/public/icon/logo-animated.svg rename packages/extension/src/{utils => inject}/dev-tools-hook.ts (84%) diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml new file mode 100644 index 00000000..199758ea --- /dev/null +++ b/.github/workflows/build-extension.yml @@ -0,0 +1,66 @@ +name: Build Extension + +on: + push: + branches: + - main + paths: + - 'packages/extension/**' + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: main + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Setup PNPM + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: Cache pnpm store + uses: actions/cache@v3 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Get package info + id: package + run: | + echo "name=$(node -p "require('./packages/extension/package.json').name")" >> $GITHUB_OUTPUT + echo "version=$(node -p "require('./packages/extension/package.json').version")" >> $GITHUB_OUTPUT + + - name: Build extensions + run: | + cd packages/extension + pnpm pack:all + + - name: Commit changes + if: github.ref == 'refs/heads/main' + run: | + git checkout main + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add -f packages/extension/build/*.zip + git diff --staged --quiet || (git commit -m "chore: update extension builds [skip ci]" && git push) diff --git a/.gitignore b/.gitignore index aa81fb6f..3bac6136 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ node_modules .env dist **/*.tgz +packages/extension/build/ +!packages/extension/build/react-scan-extension.zip +*.log +build diff --git a/CHROME_EXTENSION_GUIDE.md b/CHROME_EXTENSION_GUIDE.md index 7d42de61..9b95f79c 100644 --- a/CHROME_EXTENSION_GUIDE.md +++ b/CHROME_EXTENSION_GUIDE.md @@ -1,10 +1,27 @@ -# Chrome Extension Installation Guide +# BrowserExtension Installation Guide > [!WARNING] -> React Scan's Chrome extension is still pending approval from the Chrome Web Store. Below is a guide to installing the extension manually. +> React Scan's Browser extension is still pending approvals from the Chrome Web Store, Firefox Add-ons, and Brave Browser. Below is a guide to installing the extension manually. -1. Download the [`dist.zip`](https://github.com/aidenybai/react-scan/blob/main/packages/extension/dist.zip?raw=true) file. +## Chrome + +1. Download the [`chrome-react-scanner-extension-v1.0.0.zip`](https://github.com/aidenybai/react-scan/blob/main/packages/extension/build/chrome-react-scanner-extension-v1.0.0.zip?raw=true) file. 2. Unzip the file. 3. Open Chrome and navigate to `chrome://extensions/`. 4. Enable "Developer mode" if it is not already enabled. 5. Click "Load unpacked" and select the unzipped folder (or drag the folder into the page). + +## Firefox + +1. Download the [`firefox-react-scanner-extension-v1.0.0.zip`](https://github.com/aidenybai/react-scan/blob/main/packages/extension/build/firefox-react-scanner-extension-v1.0.0.zip?raw=true) file. +2. Unzip the file. +3. Open Firefox and navigate to `about:debugging#/runtime/this-firefox`. +4. Click "Load Temporary Add-on..." +5. Select `manifest.json` from the unzipped folder + +## Brave + +1. Download the [`brave-react-scanner-extension-v1.0.0.zip`](https://github.com/aidenybai/react-scan/blob/main/packages/extension/build/brave-react-scanner-extension-v1.0.0.zip?raw=true) file. +2. Unzip the file. +3. Open Brave and navigate to `brave://extensions`. +4. Click "Load unpacked" and select the unzipped folder (or drag the folder into the page). diff --git a/packages/extension/.env.example b/packages/extension/.env.example index ccde2697..aa0d82c8 100644 --- a/packages/extension/.env.example +++ b/packages/extension/.env.example @@ -1,3 +1,17 @@ -# Path to the Chromium binary -# You only need to set this if you're not using Google Chrome -CHROMIUM_BINARY="/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" +# Browser binary paths +# You only need to set these if the browsers are not in standard locations + +# For macOS, use paths like: +BRAVE_BINARY="/Applications/Brave\ Browser.app/Contents/MacOS/Brave\ Browser" +CHROME_BINARY="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" +FIREFOX_BINARY="/Applications/Firefox.app/Contents/MacOS/firefox-bin" + +# For Windows, use paths like: +# BRAVE_BINARY="C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" +# CHROME_BINARY="C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" +# FIREFOX_BINARY="C:\\Program Files\\Mozilla Firefox\\firefox.exe" + +# For Linux, use paths like: +# BRAVE_BINARY="/usr/bin/brave" +# CHROME_BINARY="/usr/bin/google-chrome" +# FIREFOX_BINARY="/usr/bin/firefox" diff --git a/packages/extension/README.md b/packages/extension/README.md index 00d7bdd4..3256bdae 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -1 +1,71 @@ -WIP +# React Scanner Extension + +Browser extension for scanning React applications and identifying performance issues. + + +### Environment Variables + +When developing with Brave, you need to set the `BRAVE_BINARY` environment variable. Create a `.env` file (copy from `.env.example`): + +```env +# For macOS +BRAVE_BINARY="/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" + +# For Windows +BRAVE_BINARY="C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" + +# For Linux +BRAVE_BINARY="/usr/bin/brave" +``` + +### Development Setup +#### For Chrome +1. Run development server: + ```bash + pnpm dev + ``` +3. This will automatically open Chrome with the extension loaded. + +If you need to inspect the extension, open `chrome://extensions` in Chrome +#### For Firefox + +
+ +#### For Firefox +1. Run development server: + ```bash + pnpm dev:firefox + ``` +2. This will automatically open Firefox with the extension loaded. + +If you need to inspect the extension, open `about:debugging#/runtime/this-firefox` in Firefox + +
+ +#### For Brave + +1. Run development server: + ```bash + pnpm dev:brave + ``` + +2. This will automatically open Brave with the extension loaded. + +If you need to inspect the extension, open `brave://extensions` in Brave + +
+ +### Building for Production + +To build the extension for all browsers: + +```bash +pnpm pack:all +``` + +This will create: +- `chrome-react-scanner-extension-v1.0.0.zip` +- `firefox-react-scanner-extension-v1.0.0.zip` +- `brave-react-scanner-extension-v1.0.0.zip` + +in the `build` directory. diff --git a/packages/extension/package.json b/packages/extension/package.json index ea0f2f5b..d34c570d 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -4,8 +4,17 @@ "private": true, "type": "module", "scripts": { - "build": "tsc && vite build", - "dev": "dotenv -- vite build --watch --mode development" + "clean": "rm -rf dist", + "build": "vite build", + "dev": "pnpm dev:chrome", + "dev:chrome": "cross-env BROWSER=chrome vite", + "dev:firefox": "cross-env BROWSER=firefox vite", + "dev:brave": "cross-env BROWSER=brave vite", + "mkdir": "mkdir -p build", + "pack:chrome": "pnpm clean && pnpm build && pnpm mkdir && bestzip build/chrome-$npm_package_name-v$npm_package_version.zip dist/*", + "pack:firefox": "pnpm clean && BROWSER=firefox vite build && pnpm mkdir && bestzip build/firefox-$npm_package_name-v$npm_package_version.zip dist/*", + "pack:brave": "pnpm clean && BROWSER=brave vite build && pnpm mkdir && bestzip build/brave-$npm_package_name-v$npm_package_version.zip dist/*", + "pack:all": "rm -rf build && pnpm pack:chrome && pnpm pack:firefox && pnpm pack:brave" }, "dependencies": { "react": "^18.2.0", @@ -18,10 +27,11 @@ "@types/react-dom": "^18.0.9", "@types/webextension-polyfill": "^0.10.0", "@vitejs/plugin-react": "^4.2.1", - "dotenv-cli": "^7.4.2", + "bestzip": "^2.2.1", + "cross-env": "^7.0.3", "typescript": "~5.6.3", "vite": "^5.4.3", - "vite-plugin-web-extension": "^4.0.0", + "vite-plugin-web-extension": "^4.3.1", "webextension-polyfill": "^0.10.0" } } diff --git a/packages/extension/public/icon/logo-animated.svg b/packages/extension/public/icon/logo-animated.svg new file mode 100644 index 00000000..da533b01 --- /dev/null +++ b/packages/extension/public/icon/logo-animated.svg @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/packages/extension/src/background/index.ts b/packages/extension/src/background/index.ts index 405a28cf..86d5b57f 100644 --- a/packages/extension/src/background/index.ts +++ b/packages/extension/src/background/index.ts @@ -3,19 +3,97 @@ import { STORAGE_KEY } from '../utils/constants'; import { isInternalUrl } from '../utils/helpers'; import { updateBadge } from './update-badge'; +const isFirefox = browser.runtime.getURL('').startsWith('moz-extension://'); +const browserAction = browser.action || browser.browserAction; + +const isContentScriptInjected = async (tabId: number): Promise => { + try { + await browser.tabs.sendMessage(tabId, { type: 'react-scan:ping' }); + return true; + } catch { + return false; + } +}; + +const injectContentScript = async (tabId: number) => { + try { + const tab = await browser.tabs.get(tabId); + if (!tab.url || isInternalUrl(tab.url)) { + return; + } + + if (isFirefox) { + await browser.tabs.executeScript(tabId, { + file: '/src/inject/index.js', + runAt: 'document_start', + allFrames: true + }); + + await browser.tabs.executeScript(tabId, { + file: '/src/content/index.js', + runAt: 'document_start', + allFrames: true, + matchAboutBlank: true + }); + } else { + await browser.scripting.executeScript({ + target: { + tabId, + allFrames: false + }, + files: ['/src/content/index.js'] + }); + } + } catch { + // Silent fail + } +}; + +// Firefox CSP handling +let cspListener: ((details: browser.WebRequest.OnHeadersReceivedDetailsType) => browser.WebRequest.BlockingResponse) | undefined; + +const handleFirefoxCSP = (enable: boolean) => { + if (enable) { + cspListener = (details) => { + const headers = details.responseHeaders || []; + return { + responseHeaders: headers.filter((header) => + header.name.toLowerCase() !== 'content-security-policy' + ) + }; + }; + + browser.webRequest.onHeadersReceived.addListener( + cspListener, + { urls: [""], types: ["main_frame", "script"] }, + ["blocking", "responseHeaders"] + ); + } else if (cspListener) { + browser.webRequest.onHeadersReceived.removeListener(cspListener); + cspListener = undefined; + } +}; + +// Chrome CSP handling +const handleChromeCSP = async (enable: boolean) => { + await browser.declarativeNetRequest.updateEnabledRulesets({ + [enable ? 'enableRulesetIds' : 'disableRulesetIds']: ['react_scan_csp_rules'], + }); +}; + +// Common CSP handling +const handleCSP = async (enable: boolean) => { + isFirefox ? handleFirefoxCSP(enable) : await handleChromeCSP(enable); +}; const changeCSPRules = async (domain: string, isEnabled: boolean, tabId?: number) => { let currentDomains = (await browser.storage.local.get(STORAGE_KEY))[STORAGE_KEY] || {}; if (isEnabled) { - await browser.declarativeNetRequest.updateEnabledRulesets({ - enableRulesetIds: ['react_scan_csp_rules'], - }); + await handleCSP(true); currentDomains[domain] = true; } else { - await browser.declarativeNetRequest.updateEnabledRulesets({ - disableRulesetIds: ['react_scan_csp_rules'], - }); + await handleCSP(false); const { [domain]: _, ...rest } = currentDomains; currentDomains = rest; } @@ -24,88 +102,81 @@ const changeCSPRules = async (domain: string, isEnabled: boolean, tabId?: number await updateBadge(isEnabled); if (tabId) { - await browser.tabs.sendMessage(tabId, { - type: 'CSP_RULES_CHANGED', - data: { - enabled: isEnabled, - domain, - }, - }); + try { + await browser.tabs.reload(tabId); + } catch { + // Silent fail if tab doesn't exist anymore + } } }; // Handle extension icon click -browser.action.onClicked.addListener(async (tab) => { +browserAction.onClicked.addListener(async (tab) => { if (!tab.id || !tab.url || isInternalUrl(tab.url)) { return; } const checkReactVersion = async (tabId: number) => { - const response = await new Promise<{ isReactDetected: boolean; version: string }>((resolve) => { - browser.tabs.sendMessage(tabId, { - type: 'CHECK_REACT_VERSION' - }).then(resolve); - }); - return response; - }; - - const isContentScriptInjected = async (tabId: number): Promise => { try { - await browser.tabs.sendMessage(tabId, { type: 'PING' }); - return true; - } catch { - return false; - } - }; + const isLoaded = await isContentScriptInjected(tabId); + if (!isLoaded) { + await injectContentScript(tabId); + await new Promise(resolve => setTimeout(resolve, 100)); + } - const injectContentScript = async (tabId: number) => { - if (!tab.url?.startsWith('chrome://')) { - const isInjected = await isContentScriptInjected(tabId); - if (!isInjected) { - await browser.scripting.executeScript({ - target: { tabId }, - files: ['src/content.js'] + return new Promise<{ isReactDetected: boolean; version: string }>((resolve) => { + const timeoutId = setTimeout(() => { + resolve({ isReactDetected: false, version: 'Not Found' }); + }, 1000); + + browser.tabs.sendMessage(tabId, { + type: 'react-scan:check-version' + }).then((response: { isReactDetected: boolean; version: string } | undefined) => { + clearTimeout(timeoutId); + + if (response && typeof response === 'object') { + resolve(response); + } else { + resolve({ isReactDetected: false, version: 'Not Found' }); + } + }).catch(() => { + clearTimeout(timeoutId); + resolve({ isReactDetected: false, version: 'Not Found' }); }); - } + }); + } catch (error) { + console.error('Error checking React version:', error); + return { isReactDetected: false, version: 'Not Found' }; } }; try { - // Always try to inject first - await injectContentScript(tab.id); - - // Give it a moment to initialize - await new Promise(resolve => setTimeout(resolve, 100)); - - // Then check React version const response = await checkReactVersion(tab.id); const domain = new URL(tab.url).origin; const currentDomains = (await browser.storage.local.get(STORAGE_KEY))[STORAGE_KEY] || {}; const isEnabled = domain in currentDomains && currentDomains[domain] === true; - if (!response.isReactDetected) { - // If React is not detected, ensure CSP rules are disabled + if (!response?.isReactDetected) { if (isEnabled) { await changeCSPRules(domain, false, tab.id); } return; } - // Enable or toggle CSP rules for React projects await changeCSPRules(domain, !isEnabled, tab.id); } catch (error) { - // Silent fail + console.error('Click handler error:', error); } }); // Handle CSP rules changes browser.runtime.onMessage.addListener(async (message) => { - if (message.type === 'CSP_RULES_CHANGED') { + if (message.type === 'react-scan:csp-rules-changed') { await changeCSPRules(message.data.domain, message.data.enabled); } - if (message.type === 'IS_CSP_RULES_ENABLED') { + if (message.type === 'react-scan:is-csp-rules-enabled') { const currentDomains = (await browser.storage.local.get(STORAGE_KEY))[STORAGE_KEY] || {}; const isEnabled = message.data.domain in currentDomains && currentDomains[message.data.domain] === true; return { enabled: isEnabled }; @@ -121,20 +192,19 @@ const handleTabCSPRules = async (tab: browser.Tabs.Tab) => { const currentDomains = (await browser.storage.local.get(STORAGE_KEY))[STORAGE_KEY] || {}; const isEnabled = domain in currentDomains && currentDomains[domain] === true; + // Only inject content script if CSP rules are enabled if (isEnabled) { - await browser.declarativeNetRequest.updateEnabledRulesets({ - enableRulesetIds: ['react_scan_csp_rules'], - }); - } else { - await browser.declarativeNetRequest.updateEnabledRulesets({ - disableRulesetIds: ['react_scan_csp_rules'], - }); + const isLoaded = await isContentScriptInjected(tab.id); + if (!isLoaded) { + await injectContentScript(tab.id); + } } + // Always update badge await updateBadge(isEnabled); }; -// Listen for tab updates +// Listen for tab updates - only handle complete state browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { if (changeInfo.status === 'complete') { await handleTabCSPRules(tab); diff --git a/packages/extension/src/background/update-badge.ts b/packages/extension/src/background/update-badge.ts index f79225b7..f564c439 100644 --- a/packages/extension/src/background/update-badge.ts +++ b/packages/extension/src/background/update-badge.ts @@ -2,6 +2,9 @@ import browser from 'webextension-polyfill'; import { isInternalUrl } from '../utils/helpers'; import { STORAGE_KEY } from '../utils/constants'; +const isFirefox = browser.runtime.getURL('').startsWith('moz-extension://'); +const browserAction = browser.action || browser.browserAction; + const ANIMATION_CONFIG = { size: 24, lineWidth: 1.5, @@ -9,11 +12,11 @@ const ANIMATION_CONFIG = { borderRadius: 3, dashLength: 8, gapLength: 14, - duration: 1500, + duration: 1800, color: '#A295EE', } as const; -let animationInterval: any | null = null; +let animationInterval: TTimer = null; const drawBadgeIcon = async ( ctx: OffscreenCanvasRenderingContext2D, @@ -56,9 +59,35 @@ const startAnimation = (duration: number) => { animationInterval = setInterval(animateBadge, interval); }; +type ImageDataType = { + width: number; + height: number; + data: Uint8ClampedArray; + colorSpace: 'srgb'; +}; + + export const updateBadge = async (enabled: boolean | null): Promise => { - const { size, lineWidth, inset, borderRadius, duration, color } = - ANIMATION_CONFIG; + const { duration } = ANIMATION_CONFIG; + + if (isFirefox) { + await browserAction.setIcon({ + path: enabled + ? browser.runtime.getURL('icon/logo-animated.svg') + : browser.runtime.getURL('icon/128.png') + }); + + // Start animation for Firefox + if (enabled && !animationInterval) { + startAnimation(duration); + } else if (!enabled) { + clearBadgeAnimation(); + } + + return duration; + } + + const { size, lineWidth, inset, borderRadius, color } = ANIMATION_CONFIG; const canvas = new OffscreenCanvas(size, size); const ctx = canvas.getContext('2d'); if (!ctx) { @@ -91,8 +120,15 @@ export const updateBadge = async (enabled: boolean | null): Promise => { ctx.stroke(); } - await chrome.action.setIcon({ - imageData: ctx.getImageData(0, 0, size, size), + await browserAction.setIcon({ + imageData: { + [size]: { + width: size, + height: size, + data: ctx.getImageData(0, 0, size, size).data, + colorSpace: 'srgb' + } + } }); return duration; diff --git a/packages/extension/src/content/index.ts b/packages/extension/src/content/index.ts index 451ce51d..8c2b7134 100644 --- a/packages/extension/src/content/index.ts +++ b/packages/extension/src/content/index.ts @@ -1,56 +1,49 @@ import styles from '../assets/css/styles.css?inline'; -import { IncomingMessageSchema } from '../types/messages'; -import { loadCss } from '../utils/helpers'; +import { BroadcastSchema } from '../types/messages'; +import { loadCss, broadcast } from '../utils/helpers'; +chrome.runtime.onMessage.addListener((message: unknown, _sender, sendResponse) => { + const result = BroadcastSchema.safeParse(message); -chrome.runtime.onMessage.addListener((message: unknown, sender, sendResponse) => { - const { data, error } = IncomingMessageSchema.safeParse(message); - if (error) { - // Silent fail - return; - } + if (result.success) { + const data = result.data; - if (data.type === 'CHECK_REACT_VERSION') { - window.addEventListener('react-scan:version-check-result', (event: Event) => { - const { isReactDetected, version } = (event as CustomEvent).detail; - sendResponse({ isReactDetected, version }); - }, { once: true }); + if (data.type === 'react-scan:ping') { + sendResponse(true); + return false; + } - window.dispatchEvent(new CustomEvent('react-scan:check-version')); - return true; - } + if (data.type === 'react-scan:check-version') { + broadcast.onmessage = (type, msgData) => { + if (type === 'react-scan:update') { + const response = { + isReactDetected: !['Unknown', 'Not Found'].includes(msgData.reactVersion), + version: msgData.reactVersion + }; + sendResponse(response); + } + }; - if (data.type === 'PING') { - sendResponse(true); - return; - } + // Send the check message + broadcast.postMessage('react-scan:check-version', {}); + + return true; // Keep the message channel open + } - switch (data.type) { - case 'CSP_RULES_CHANGED': + if (data.type === 'react-scan:csp-rules-changed') { window.location.reload(); - break; - case 'IS_CSP_RULES_ENABLED': - window.dispatchEvent( - new CustomEvent('react-scan:is-csp-rules-enabled', { - detail: { ...data.data }, - }), - ); - break; + } } + return true; }); const getCSPRulesState = async () => chrome.runtime.sendMessage({ - type: 'IS_CSP_RULES_ENABLED', + type: 'react-scan:is-csp-rules-enabled', data: { domain: window.location.origin, }, }).then((cspRulesEnabled) => { - window.dispatchEvent( - new CustomEvent('react-scan:is-csp-rules-enabled', { - detail: { ...cspRulesEnabled }, - }), - ); - + broadcast.postMessage('react-scan:is-csp-rules-enabled', cspRulesEnabled); return cspRulesEnabled.enabled; }); @@ -78,16 +71,18 @@ const init = (() => { const isCSPRulesEnabled = await getCSPRulesState(); const toolbarContent = document.getElementById('react-scan-toolbar-content'); - window.dispatchEvent( - new CustomEvent('react-scan:state-change', { - detail: { enabled: isCSPRulesEnabled } - }) - ); + // window.dispatchEvent( + // new CustomEvent('react-scan:state-change', { + // detail: { enabled: isCSPRulesEnabled } + // }) + // ); + + // broadcast.postMessage('react-scan:state-change', { enabled: isCSPRulesEnabled }); if (isCSPRulesEnabled) { // send message to the extension to disable the rules await chrome.runtime.sendMessage({ - type: 'CSP_RULES_CHANGED', + type: 'react-scan:csp-rules-changed', data: { enabled: false, domain: window.location.origin, @@ -96,7 +91,7 @@ const init = (() => { } else { // send message to the extension to enable the rules await chrome.runtime.sendMessage({ - type: 'CSP_RULES_CHANGED', + type: 'react-scan:csp-rules-changed', data: { enabled: true, domain: window.location.origin, @@ -131,10 +126,10 @@ const init = (() => { }; })(); -window.addEventListener('react-scan:update', ((event: Event) => { - const customEvent = event as CustomEvent; - - if (!['Unknown', 'Not Found'].includes(customEvent.detail.reactVersion)) { - void init(); +broadcast.onmessage = (type, data) => { + if (type === 'react-scan:update') { + if (!['Unknown', 'Not Found'].includes(data.reactVersion)) { + void init(); + } } -}) as EventListener); +}; diff --git a/packages/extension/src/utils/dev-tools-hook.ts b/packages/extension/src/inject/dev-tools-hook.ts similarity index 84% rename from packages/extension/src/utils/dev-tools-hook.ts rename to packages/extension/src/inject/dev-tools-hook.ts index 7f26fdc8..798c395c 100644 --- a/packages/extension/src/utils/dev-tools-hook.ts +++ b/packages/extension/src/inject/dev-tools-hook.ts @@ -1,4 +1,4 @@ -import { NO_OP } from './helpers'; +import { NO_OP } from "../utils/helpers"; type Hook = { checkDCE: () => void; @@ -45,6 +45,11 @@ export const registerDevtoolsHook = ({ return devtoolsHook; }; -registerDevtoolsHook({ - onCommitFiberRoot: NO_OP as Hook['onCommitFiberRoot'], -}); + + +// Install hook if not already present +if (!window.__REACT_DEVTOOLS_GLOBAL_HOOK__) { + registerDevtoolsHook({ + onCommitFiberRoot: NO_OP as Hook['onCommitFiberRoot'], + }); +} diff --git a/packages/extension/src/inject/index.ts b/packages/extension/src/inject/index.ts index e68a2002..58d01d5d 100644 --- a/packages/extension/src/inject/index.ts +++ b/packages/extension/src/inject/index.ts @@ -1,7 +1,5 @@ -import '../utils/dev-tools-hook'; - import noReactStyles from '../assets/css/no-react.css?inline'; -import { getReactVersion, loadCss } from '../utils/helpers'; +import { getReactVersion, loadCss, broadcast } from '../utils/helpers'; import { CACHE_TTL, CACHE_NAME } from '../utils/constants'; const scriptsToInject = [ @@ -88,33 +86,9 @@ const injectReactScan = async () => { } }; -window.addEventListener('react-scan:is-csp-rules-enabled', (event) => { - const cspRulesEnabled = (event as CustomEvent).detail.enabled; - - if (cspRulesEnabled) { - void injectReactScan(); - } -}); - -window.addEventListener('react-scan:state-change', (event: Event) => { - const { enabled } = (event as CustomEvent).detail; - if ( - typeof window.__REACT_SCAN__?.ReactScanInternals === 'object' && - window.__REACT_SCAN__?.ReactScanInternals !== null - ) { - window.__REACT_SCAN__.ReactScanInternals.isPaused = enabled; - } -}); - window.addEventListener('DOMContentLoaded', async () => { - const version = await getReactVersion(); - window.dispatchEvent( - new CustomEvent('react-scan:update', { - detail: { - reactVersion: version, - }, - }), - ); + const reactVersion = await getReactVersion(); + broadcast.postMessage('react-scan:update', { reactVersion }); }); (() => { @@ -157,19 +131,34 @@ window.addEventListener('DOMContentLoaded', async () => { document.documentElement.appendChild(fragment); - window.addEventListener('react-scan:check-version', async () => { - const version = await getReactVersion(); - const isReactDetected = !['Unknown', 'Not Found'].includes(version); + broadcast.onmessage = async (type, data) => { + if (type === 'react-scan:is-csp-rules-enabled') { + const cspRulesEnabled = data.enabled; + if (cspRulesEnabled) { + void injectReactScan(); + } + } + + if (type === 'react-scan:state-change') { + if ( + typeof window.__REACT_SCAN__?.ReactScanInternals === 'object' && + window.__REACT_SCAN__?.ReactScanInternals !== null + ) { + window.__REACT_SCAN__.ReactScanInternals.isPaused = data.enabled; + } + } - window.dispatchEvent( - new CustomEvent('react-scan:version-check-result', { - detail: { isReactDetected, version } - }) - ); + if (type === 'react-scan:check-version') { + const reactVersion = await getReactVersion(); + const isReactDetected = !['Unknown', 'Not Found'].includes(reactVersion); - if (!isReactDetected) { - document.documentElement.classList.add('freeze'); - backdrop.className = 'animate-fade-in'; + + if (!isReactDetected) { + document.documentElement.classList.add('freeze'); + backdrop.className = 'animate-fade-in'; + } + + broadcast.postMessage('react-scan:update', { reactVersion }); } - }); + }; })(); diff --git a/packages/extension/src/manifest.json b/packages/extension/src/manifest.json index 9462432d..c767f1e9 100644 --- a/packages/extension/src/manifest.json +++ b/packages/extension/src/manifest.json @@ -11,6 +11,12 @@ "96": "icon/96.png", "128": "icon/128.png" }, + "{{firefox}}.browser_specific_settings": { + "gecko": { + "id": "react-scan@million.dev", + "strict_min_version": "57.0" + } + }, "{{chrome}}.action": { "default_icon": { "16": "icon/16.png", @@ -35,15 +41,26 @@ "src/background/index.ts" ] }, - "permissions": [ + "{{chrome}}.permissions": [ + "activeTab", + "tabs", + "scripting", + "storage", + "declarativeNetRequest" + ], + "{{firefox}}.permissions": [ "activeTab", "tabs", "scripting", - "declarativeNetRequest", - "storage" + "storage", + "webRequest", + "webRequestBlocking", + "" ], - "host_permissions": [""], - "declarative_net_request": { + "{{chrome}}.host_permissions": [ + "" + ], + "{{chrome}}.declarative_net_request": { "rule_resources": [ { "id": "react_scan_csp_rules", @@ -52,12 +69,27 @@ } ] }, + "{{firefox}}.content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "content_scripts": [ { "matches": [""], + "js": [ + "src/inject/dev-tools-hook.ts" + ], + "run_at": "document_start", + "world": "MAIN", + "{{firefox}}.all_frames": true, + "{{firefox}}.match_about_blank": true + }, + { + "matches": [ + "" + ], "js": ["src/inject/index.ts"], "run_at": "document_start", - "world": "MAIN" + "world": "MAIN", + "{{firefox}}.all_frames": true, + "{{firefox}}.match_about_blank": true }, { "matches": [""], @@ -67,15 +99,18 @@ "run_at": "document_start" } ], - "web_accessible_resources": [ + "{{chrome}}.web_accessible_resources": [ { "resources": [ - "src/content.js", "icon/*" ], "matches": [ "" ] } + ], + "{{firefox}}.web_accessible_resources": [ + "src/content.js", + "icon/*" ] } diff --git a/packages/extension/src/types/global.d.ts b/packages/extension/src/types/global.d.ts index 3f9087c3..d6bc28fd 100644 --- a/packages/extension/src/types/global.d.ts +++ b/packages/extension/src/types/global.d.ts @@ -1,22 +1,31 @@ -interface Window { - __REACT_SCAN__?: { - ReactScanInternals?: { - isPaused: boolean; +declare global { + type BroadcastHandler = (type: BroadcastMessage['type'], data: Extract['data']) => void; + + interface Window { + __REACT_SCAN__?: { + ReactScanInternals?: { + isPaused: boolean; + }; }; - }; - __REACT_DEVTOOLS_GLOBAL_HOOK__?: { - renderers: Map; - supportsFiber: boolean; - checkDCE: () => void; - onCommitFiberRoot: (rendererID: number, root: unknown) => void; - onCommitFiberUnmount: () => void; - onScheduleFiberRoot: () => void; - inject: (renderer: unknown) => number; - }; -} + __REACT_DEVTOOLS_GLOBAL_HOOK__?: { + renderers: Map; + supportsFiber: boolean; + checkDCE: () => void; + onCommitFiberRoot: (rendererID: number, root: unknown) => void; + onCommitFiberUnmount: () => void; + onScheduleFiberRoot: () => void; + inject: (renderer: unknown) => number; + }; + wrappedJSObject?: any; + } -declare global { - interface Global { - __REACT_DEVTOOLS_GLOBAL_HOOK__: Window['__REACT_DEVTOOLS_GLOBAL_HOOK__']; + interface globalThis { + __REACT_SCAN__?: Window['__REACT_SCAN__']; + __REACT_DEVTOOLS_GLOBAL_HOOK__?: Window['__REACT_DEVTOOLS_GLOBAL_HOOK__']; } + + var __REACT_DEVTOOLS_GLOBAL_HOOK__: Window['__REACT_DEVTOOLS_GLOBAL_HOOK__']; + type TTimer = ReturnType | ReturnType | null; } + +export {}; diff --git a/packages/extension/src/types/messages.ts b/packages/extension/src/types/messages.ts index 6b5d4e7b..9e2e9795 100644 --- a/packages/extension/src/types/messages.ts +++ b/packages/extension/src/types/messages.ts @@ -1,36 +1,39 @@ import { z } from 'zod'; -declare global { - // eslint-disable-next-line no-var - var __REACT_DEVTOOLS_GLOBAL_HOOK__: Window['__REACT_DEVTOOLS_GLOBAL_HOOK__']; - type TTimer = ReturnType; -} - -export const IncomingMessageSchema = z.discriminatedUnion('type', [ +export const BroadcastSchema = z.discriminatedUnion('type', [ z.object({ - type: z.literal('PING'), + type: z.literal('react-scan:ping'), }), z.object({ - type: z.literal('CSP_RULES_CHANGED'), + type: z.literal('react-scan:csp-rules-changed'), data: z.object({ - enabled: z.boolean(), domain: z.string(), + enabled: z.boolean(), }), }), z.object({ - type: z.literal('IS_CSP_RULES_ENABLED'), + type: z.literal('react-scan:is-csp-rules-enabled'), data: z.object({ domain: z.string(), + enabled: z.boolean(), + }), + }), + z.object({ + type: z.literal('react-scan:check-version'), + }), + z.object({ + type: z.literal('react-scan:update'), + data: z.object({ + reactVersion: z.string(), + isReactDetected: z.boolean().optional(), }), }), z.object({ - type: z.literal('CHECK_REACT_VERSION'), + type: z.literal('react-scan:state-change'), + data: z.object({ + enabled: z.boolean(), + }), }), ]); -export type IncomingMessage = z.infer; - -export type OutgoingMessage = { - type: 'SCAN_UPDATE'; - reactVersion: string; -}; +export type BroadcastMessage = z.infer; diff --git a/packages/extension/src/utils/constants.ts b/packages/extension/src/utils/constants.ts index 1c8090d6..9afec55d 100644 --- a/packages/extension/src/utils/constants.ts +++ b/packages/extension/src/utils/constants.ts @@ -1,3 +1,5 @@ export const STORAGE_KEY = 'react-scan-allowed-domains'; export const CACHE_NAME = 'react-scan-cache'; export const CACHE_TTL = 8 * 60 * 60 * 1000; // 8 hours in milliseconds + +export const broadcastChannel = new BroadcastChannel('react-scan'); diff --git a/packages/extension/src/utils/helpers.ts b/packages/extension/src/utils/helpers.ts index 04bc2844..d1a4f3ca 100644 --- a/packages/extension/src/utils/helpers.ts +++ b/packages/extension/src/utils/helpers.ts @@ -1,3 +1,5 @@ +import { broadcastChannel } from "./constants"; + export const NO_OP = () => { /**/ }; @@ -45,3 +47,14 @@ export const getReactVersion = (retries = 10, delay = 10): Promise => { check(); }); }; + +export const broadcast = { + postMessage: (type: string, data: unknown) => { + broadcastChannel.postMessage({ type, data }); + }, + set onmessage(handler: BroadcastHandler | null) { + broadcastChannel.onmessage = handler + ? (event) => handler(event.data.type, event.data.data) + : null; + } +}; diff --git a/packages/extension/vite.config.ts b/packages/extension/vite.config.ts index 7849c667..a3401492 100644 --- a/packages/extension/vite.config.ts +++ b/packages/extension/vite.config.ts @@ -1,38 +1,94 @@ -import { defineConfig } from 'vite'; +import { defineConfig, UserConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react'; import webExtension, { readJsonFile } from 'vite-plugin-web-extension'; -function generateManifest() { - const manifest = readJsonFile('src/manifest.json'); - const pkg = readJsonFile('package.json'); - return { - name: pkg.name, - description: pkg.description, - version: pkg.version, - ...manifest, +// Browser types +const BROWSER_TYPES = { + CHROME: 'chrome', + FIREFOX: 'firefox', + BRAVE: 'brave', +} as const; + +export default defineConfig(({ mode }): UserConfig => { + const env = loadEnv(mode, process.cwd(), ''); + const browser = env.BROWSER || BROWSER_TYPES.CHROME; + + const isBrave = browser === BROWSER_TYPES.BRAVE; + + // Validate Brave binary + if (env.NODE_ENV === 'development' && isBrave && !env.BRAVE_BINARY) { + console.error(` + ⚛️ React Scan + ============== + 🚫 Error: BRAVE_BINARY environment variable is missing + + This is required for Brave browser development. + Please check .env.example and set up your .env file with the correct path: + + 📍 For macOS: + BRAVE_BINARY="/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" + + 📍 For Windows: + BRAVE_BINARY="C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" + + 📍 For Linux: + BRAVE_BINARY="/usr/bin/brave" + =============== +`); + process.exit(0); + } + + // Get browser binary based on type + const getBrowserBinary = () => { + switch (browser) { + case BROWSER_TYPES.FIREFOX: + return env.FIREFOX_BINARY; + case BROWSER_TYPES.BRAVE: + return env.BRAVE_BINARY; + case BROWSER_TYPES.CHROME: + return env.CHROME_BINARY; + default: + return env.CHROME_BINARY; + } }; -} - -// https://vitejs.dev/config/ -export default defineConfig({ - build: { - minify: 'terser', - terserOptions: { - keep_fnames: true, + + // Generate manifest with package info + function generateManifest() { + const manifest = readJsonFile('src/manifest.json'); + const pkg = readJsonFile('package.json'); + + return { + name: pkg.name, + description: pkg.description, + version: pkg.version, + ...manifest, + }; + } + + // Vite configuration + return { + build: { + minify: 'esbuild' as const, + }, + esbuild: { + keepNames: true, + minifyIdentifiers: false, }, - }, - plugins: [ - react(), - webExtension({ - manifest: generateManifest, - webExtConfig: { - startUrl: 'https://github.com/aidenybai/react-scan', - chromiumBinary: process.env.CHROMIUM_BINARY, - }, - }), - ], - esbuild: { - minifyIdentifiers: false, - keepNames: true, - }, + plugins: [ + react(), + webExtension({ + manifest: generateManifest, + // Use Chrome config for Brave + browser: isBrave ? BROWSER_TYPES.CHROME : browser, + webExtConfig: { + browser: isBrave ? BROWSER_TYPES.CHROME : browser, + target: isBrave ? BROWSER_TYPES.CHROME : browser, + chromiumBinary: getBrowserBinary(), + firefoxBinary: env.FIREFOX_BINARY, + braveBinary: env.BRAVE_BINARY, + startUrl: ['https://github.com/aidenybai/react-scan'], + }, + }), + ], + }; }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24c4a315..2a4c4677 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,9 +45,12 @@ importers: '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.3.1(vite@5.4.3(@types/node@22.10.1)(terser@5.36.0)) - dotenv-cli: - specifier: ^7.4.2 - version: 7.4.2 + bestzip: + specifier: ^2.2.1 + version: 2.2.1 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 typescript: specifier: ~5.6.3 version: 5.6.3 @@ -55,8 +58,8 @@ importers: specifier: ^5.4.3 version: 5.4.3(@types/node@22.10.1)(terser@5.36.0) vite-plugin-web-extension: - specifier: ^4.0.0 - version: 4.0.0(@types/node@22.10.1)(terser@5.36.0) + specifier: ^4.3.1 + version: 4.3.1(@types/node@22.10.1)(terser@5.36.0) webextension-polyfill: specifier: ^0.10.0 version: 0.10.0 @@ -341,6 +344,10 @@ packages: resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.24.7': + resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.25.9': resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} @@ -1553,6 +1560,18 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -1641,6 +1660,14 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bestzip@2.2.1: + resolution: {integrity: sha512-XdAb87RXqOqF7C6UgQG9IqpEHJvS6IOUo0bXWEAebjSSdhDjsbcqFKdHpn5Q7QHz2pGr3Zmw4wgG3LlzdyDz7w==} + engines: {node: '>=10'} + hasBin: true + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1648,6 +1675,9 @@ packages: bippy@0.0.10: resolution: {integrity: sha512-2E8U2h/3ZltRwxfVkT1c2DqWUjgIGFRg8cD1qQDJ0m7YOJCpxqI87s+vaaEzvIxRw4MJOUVJ3OZ6K904UNX+iw==} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} @@ -1673,9 +1703,15 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -1752,8 +1788,8 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chrome-launcher@0.15.1: - resolution: {integrity: sha512-UugC8u59/w2AyX5sHLZUHoxBAiSiunUhZa3zZwMH6zPVis0C3dDKiRWyUGIo14tTbZHGVviWxv3PQWZ7taZ4fg==} + chrome-launcher@1.1.0: + resolution: {integrity: sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==} engines: {node: '>=12.13.0'} hasBin: true @@ -1776,6 +1812,9 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1805,6 +1844,10 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1839,6 +1882,20 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2001,18 +2058,6 @@ packages: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} - dotenv-cli@7.4.2: - resolution: {integrity: sha512-SbUj8l61zIbzyhIbg0FwPJq6+wjbzdn9oEtozQpZ6kW2ihCcapKVZj49oCT3oPM+mgQm+itgvUQcG5szxVrZTA==} - hasBin: true - - dotenv-expand@10.0.0: - resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} - engines: {node: '>=12'} - - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - dtrace-provider@0.8.8: resolution: {integrity: sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==} engines: {node: '>=0.10'} @@ -2029,6 +2074,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + enhanced-resolve@5.17.1: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} @@ -2364,8 +2412,8 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - firefox-profile@4.5.0: - resolution: {integrity: sha512-goE2XxbmYVSafvCjcy64/AK3xOr14HCUCD4+TpYWEIMy4nrJfNAacLGzwqKwZhCW0hHI2TYMGH+G/YBvOE8L6g==} + firefox-profile@4.6.0: + resolution: {integrity: sha512-I9rAm1w8U3CdhgO4EzTJsCvgcbvynZn9lOySkZf78wUdUIQH2w9QOKf3pAX+THt2XMSSR3kJSuM8P7bYux9j8g==} hasBin: true flat-cache@3.2.0: @@ -2386,6 +2434,9 @@ packages: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -2429,6 +2480,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} @@ -2592,6 +2647,9 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-walk@5.0.1: resolution: {integrity: sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -2767,6 +2825,14 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-primitive@3.0.1: + resolution: {integrity: sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==} + engines: {node: '>=0.10.0'} + is-regex@1.2.0: resolution: {integrity: sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==} engines: {node: '>= 0.4'} @@ -2844,6 +2910,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + iterator.prototype@1.1.3: resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} engines: {node: '>= 0.4'} @@ -2934,6 +3004,10 @@ packages: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2941,8 +3015,8 @@ packages: lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - lighthouse-logger@1.4.2: - resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + lighthouse-logger@2.0.1: + resolution: {integrity: sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==} lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} @@ -2974,12 +3048,27 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -3068,8 +3157,8 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} engines: {node: '>=10'} hasBin: true @@ -3583,6 +3672,13 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3614,6 +3710,10 @@ packages: resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} hasBin: true + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -3734,6 +3834,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + set-value@4.1.0: + resolution: {integrity: sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==} + engines: {node: '>=11.0'} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -3955,6 +4059,10 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + terser@5.36.0: resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} engines: {node: '>=10'} @@ -3994,9 +4102,9 @@ packages: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} - tmp@0.2.1: - resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} - engines: {node: '>=8.17.0'} + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -4195,8 +4303,8 @@ packages: '@nuxt/kit': optional: true - vite-plugin-web-extension@4.0.0: - resolution: {integrity: sha512-cq9eDMVplkuiiS8GlYECvt1LREHJIMIaP93J1bYcAyUJV4cSrNLfOwQt3VnYQjv+Yw0Y2Zhg5ozEA3oN9oSg8Q==} + vite-plugin-web-extension@4.3.1: + resolution: {integrity: sha512-yG/07Rzk70SxLUQIfZMbNm0472gbFPkoPCAJvcGhyblTrg0FPSKfkQJ4yug0//IxoYCaTv5WIcNJCuVntUz4rQ==} engines: {node: '>=16'} vite@5.4.3: @@ -4255,12 +4363,12 @@ packages: jsdom: optional: true - watchpack@2.4.0: - resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} + watchpack@2.4.1: + resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} - web-ext-run@0.1.2: - resolution: {integrity: sha512-VygO7lg10keonbku5Lbi21zEz1k91odkW+c6YB5DGb+mITcRMVM2zr/tJ+Suh6wxNUOOFNbinoqDlSUWdxt6YQ==} + web-ext-run@0.2.2: + resolution: {integrity: sha512-GD59q5/1wYQJXTHrljMZaBa3cCz+Jj3FMDLYgKyAa34TPcHSuMaGqp7TcLJ66PCe43C3hmbEAZd8QCpAB34eiw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} webextension-polyfill@0.10.0: @@ -4332,8 +4440,8 @@ packages: write-file-atomic@3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} - ws@8.16.0: - resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -4356,6 +4464,10 @@ packages: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -4364,6 +4476,14 @@ packages: engines: {node: '>= 14'} hasBin: true + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -4375,6 +4495,10 @@ packages: zip-dir@2.0.0: resolution: {integrity: sha512-uhlsJZWz26FLYXOD6WVuq+fIcZ3aBPGo/cFdiLlv3KNwpa52IF3ISV8fLhQLiqVu5No3VhlqlgthN6gehil1Dg==} + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -4488,6 +4612,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.24.7': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.25.9': dependencies: '@babel/code-frame': 7.26.2 @@ -5407,9 +5535,9 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint-config-prettier: 9.1.0(eslint@8.57.1) eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-playwright: 1.8.3(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1) @@ -5441,7 +5569,7 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint-config-prettier: 9.1.0(eslint@8.57.1) eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) @@ -5556,6 +5684,42 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + arg@5.0.2: {} argparse@2.0.1: {} @@ -5657,10 +5821,26 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + + bestzip@2.2.1: + dependencies: + archiver: 5.3.2 + async: 3.2.6 + glob: 7.2.3 + which: 2.0.2 + yargs: 16.2.0 + binary-extensions@2.3.0: {} bippy@0.0.10: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + bluebird@3.7.2: {} boolbase@1.0.0: {} @@ -5696,8 +5876,15 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) + buffer-crc32@0.2.13: {} + buffer-from@1.1.2: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + builtin-modules@3.3.0: {} bundle-name@4.1.0: @@ -5785,12 +5972,12 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chrome-launcher@0.15.1: + chrome-launcher@1.1.0: dependencies: '@types/node': 20.17.9 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 - lighthouse-logger: 1.4.2 + lighthouse-logger: 2.0.1 transitivePeerDependencies: - supports-color @@ -5806,6 +5993,12 @@ snapshots: client-only@0.0.1: {} + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -5834,6 +6027,13 @@ snapshots: commander@9.5.0: {} + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + concat-map@0.0.1: {} concat-stream@1.6.2: @@ -5870,6 +6070,17 @@ snapshots: core-util-is@1.0.3: {} + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -6014,17 +6225,6 @@ snapshots: dependencies: is-obj: 2.0.0 - dotenv-cli@7.4.2: - dependencies: - cross-spawn: 7.0.6 - dotenv: 16.4.5 - dotenv-expand: 10.0.0 - minimist: 1.2.8 - - dotenv-expand@10.0.0: {} - - dotenv@16.4.5: {} - dtrace-provider@0.8.8: dependencies: nan: 2.22.0 @@ -6038,6 +6238,10 @@ snapshots: emoji-regex@9.2.2: {} + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + enhanced-resolve@5.17.1: dependencies: graceful-fs: 4.2.11 @@ -6287,6 +6491,55 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.3.7 + enhanced-resolve: 5.17.1 + eslint: 8.57.1 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + fast-glob: 3.3.2 + get-tsconfig: 4.8.1 + is-bun-module: 1.3.0 + is-glob: 4.0.3 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.3.7 + enhanced-resolve: 5.17.1 + eslint: 8.57.1 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + fast-glob: 3.3.2 + get-tsconfig: 4.8.1 + is-bun-module: 1.3.0 + is-glob: 4.0.3 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 @@ -6294,7 +6547,7 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -6304,6 +6557,35 @@ snapshots: eslint: 8.57.1 ignore: 5.3.2 + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 @@ -6613,7 +6895,7 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - firefox-profile@4.5.0: + firefox-profile@4.6.0: dependencies: adm-zip: 0.5.16 fs-extra: 9.0.1 @@ -6640,6 +6922,8 @@ snapshots: form-data-encoder@2.1.4: {} + fs-constants@1.0.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -6689,6 +6973,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-func-name@2.0.2: {} get-intrinsic@1.2.4: @@ -6875,6 +7161,8 @@ snapshots: human-signals@5.0.0: {} + ieee754@1.2.1: {} + ignore-walk@5.0.1: dependencies: minimatch: 5.1.6 @@ -7017,6 +7305,12 @@ snapshots: is-plain-obj@4.1.0: {} + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-primitive@3.0.1: {} + is-regex@1.2.0: dependencies: call-bind: 1.0.7 @@ -7081,6 +7375,8 @@ snapshots: isexe@2.0.0: {} + isobject@3.0.1: {} + iterator.prototype@1.1.3: dependencies: define-properties: 1.2.1 @@ -7165,6 +7461,10 @@ snapshots: dependencies: package-json: 8.1.1 + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -7174,7 +7474,7 @@ snapshots: dependencies: immediate: 3.0.6 - lighthouse-logger@1.4.2: + lighthouse-logger@2.0.1: dependencies: debug: 2.6.9 marky: 1.2.5 @@ -7210,10 +7510,20 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.flatten@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + lodash.merge@4.6.2: {} lodash.sortby@4.7.0: {} + lodash.union@4.6.0: {} + lodash.uniq@4.5.0: {} lodash.uniqby@4.7.0: {} @@ -7288,7 +7598,7 @@ snapshots: minimist: 1.2.8 optional: true - mkdirp@1.0.4: {} + mkdirp@3.0.1: {} mlly@1.7.3: dependencies: @@ -7871,6 +8181,16 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -7908,6 +8228,8 @@ snapshots: dependencies: jsesc: 0.5.0 + require-directory@2.1.1: {} + require-from-string@2.0.2: {} resolve-alpn@1.2.1: {} @@ -8042,6 +8364,11 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + set-value@4.1.0: + dependencies: + is-plain-object: 2.0.4 + is-primitive: 3.0.1 + setimmediate@1.0.5: {} sharp@0.33.5: @@ -8331,6 +8658,14 @@ snapshots: tapable@2.2.1: {} + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -8365,9 +8700,7 @@ snapshots: tinyspy@2.2.1: {} - tmp@0.2.1: - dependencies: - rimraf: 3.0.2 + tmp@0.2.3: {} to-regex-range@5.0.1: dependencies: @@ -8619,7 +8952,7 @@ snapshots: - rollup - supports-color - vite-plugin-web-extension@4.0.0(@types/node@22.10.1)(terser@5.36.0): + vite-plugin-web-extension@4.3.1(@types/node@22.10.1)(terser@5.36.0): dependencies: ajv: 8.17.1 async-lock: 1.4.1 @@ -8630,7 +8963,7 @@ snapshots: lodash.uniqby: 4.7.0 md5: 2.3.0 vite: 5.4.3(@types/node@22.10.1)(terser@5.36.0) - web-ext-run: 0.1.2 + web-ext-run: 0.2.2 webextension-polyfill: 0.10.0 yaml: 2.6.1 transitivePeerDependencies: @@ -8737,35 +9070,36 @@ snapshots: - terser optional: true - watchpack@2.4.0: + watchpack@2.4.1: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - web-ext-run@0.1.2: + web-ext-run@0.2.2: dependencies: - '@babel/runtime': 7.23.9 + '@babel/runtime': 7.24.7 '@devicefarmer/adbkit': 3.2.6 bunyan: 1.8.15 - chrome-launcher: 0.15.1 + chrome-launcher: 1.1.0 debounce: 1.2.1 es6-error: 4.1.1 - firefox-profile: 4.5.0 + firefox-profile: 4.6.0 fs-extra: 11.2.0 fx-runner: 1.4.0 - mkdirp: 1.0.4 + mkdirp: 3.0.1 multimatch: 6.0.0 mz: 2.7.0 node-notifier: 10.0.1 parse-json: 7.1.1 promise-toolbox: 0.21.0 + set-value: 4.1.0 source-map-support: 0.5.21 strip-bom: 5.0.0 strip-json-comments: 5.0.1 - tmp: 0.2.1 + tmp: 0.2.3 update-notifier: 6.0.2 - watchpack: 2.4.0 - ws: 8.16.0 + watchpack: 2.4.1 + ws: 8.18.0 zip-dir: 2.0.0 transitivePeerDependencies: - bufferutil @@ -8868,7 +9202,7 @@ snapshots: signal-exit: 3.0.7 typedarray-to-buffer: 3.1.5 - ws@8.16.0: {} + ws@8.18.0: {} xdg-basedir@5.1.0: {} @@ -8879,10 +9213,24 @@ snapshots: xmlbuilder@11.0.1: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yaml@2.6.1: {} + yargs-parser@20.2.9: {} + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + yocto-queue@0.1.0: {} yocto-queue@1.1.1: {} @@ -8892,4 +9240,10 @@ snapshots: async: 3.2.6 jszip: 3.10.1 + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + zod@3.23.8: {}