From c9e63eebb405585fb48b6b7d7e60c39497798372 Mon Sep 17 00:00:00 2001 From: Seismix Date: Mon, 26 Jan 2026 12:56:57 +0100 Subject: [PATCH 1/7] feat: add launching native browsers from WSL --- packages/runner/src/browser-paths.ts | 8 +- packages/runner/src/index.ts | 1 + packages/wxt-demo/package.json | 1 + packages/wxt/package.json | 1 + .../src/core/runners/__tests__/index.test.ts | 46 ++- .../runners/__tests__/web-ext.wsl.test.ts | 117 ++++++ .../src/core/runners/__tests__/wsl.test.ts | 26 ++ packages/wxt/src/core/runners/index.ts | 19 +- packages/wxt/src/core/runners/web-ext.ts | 236 ++++++++++- packages/wxt/src/core/runners/wsl.ts | 8 +- packages/wxt/src/core/runners/wxt-runner.ts | 254 ++++++++++++ pnpm-lock.yaml | 374 +++++++++++++----- 12 files changed, 979 insertions(+), 112 deletions(-) create mode 100644 packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts create mode 100644 packages/wxt/src/core/runners/__tests__/wsl.test.ts create mode 100644 packages/wxt/src/core/runners/wxt-runner.ts diff --git a/packages/runner/src/browser-paths.ts b/packages/runner/src/browser-paths.ts index 47a791f52..2839a7198 100644 --- a/packages/runner/src/browser-paths.ts +++ b/packages/runner/src/browser-paths.ts @@ -41,7 +41,13 @@ export const KNOWN_BROWSER_PATHS: Record< '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chrome.app/Contents/MacOS/Google Chrome', ], - linux: [], + linux: [ + // Debian/Ubuntu official Google Chrome package + // Prefer the real binary over wrapper scripts to keep extra file descriptors open. + '/opt/google/chrome/chrome', + '/usr/bin/google-chrome', + '/usr/bin/google-chrome-stable', + ], windows: ['C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'], }, 'chrome-beta': { diff --git a/packages/runner/src/index.ts b/packages/runner/src/index.ts index 98b4a0dcc..fc1516017 100644 --- a/packages/runner/src/index.ts +++ b/packages/runner/src/index.ts @@ -1,3 +1,4 @@ export * from './run'; export * from './options'; export * from './install'; +export * from './browser-paths'; diff --git a/packages/wxt-demo/package.json b/packages/wxt-demo/package.json index 202c39285..6c8af8c14 100644 --- a/packages/wxt-demo/package.json +++ b/packages/wxt-demo/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "buildc --deps-only -- wxt", + "dev:firefox": "buildc --deps-only -- wxt -b firefox --mv2", "build": "buildc --deps-only -- wxt build", "build:all": "buildc --deps-only -- pnpm run --reporter-hide-prefix /^build:all:.*/", "build:all:chrome-mv3": "wxt build", diff --git a/packages/wxt/package.json b/packages/wxt/package.json index 99595b2b6..f3fd5151f 100644 --- a/packages/wxt/package.json +++ b/packages/wxt/package.json @@ -22,6 +22,7 @@ "@webext-core/isolated-element": "^1.1.2", "@webext-core/match-patterns": "^1.0.3", "@wxt-dev/browser": "workspace:^", + "@wxt-dev/runner": "workspace:^", "@wxt-dev/storage": "workspace:^1.0.0", "async-mutex": "^0.5.0", "c12": "^3.3.2", diff --git a/packages/wxt/src/core/runners/__tests__/index.test.ts b/packages/wxt/src/core/runners/__tests__/index.test.ts index 7b46e11bf..496bf0478 100644 --- a/packages/wxt/src/core/runners/__tests__/index.test.ts +++ b/packages/wxt/src/core/runners/__tests__/index.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { createExtensionRunner } from '..'; import { setFakeWxt } from '../../utils/testing/fake-objects'; import { mock } from 'vitest-mock-extended'; @@ -7,6 +7,7 @@ import { createWslRunner } from '../wsl'; import { createManualRunner } from '../manual'; import { isWsl } from '../../utils/wsl'; import { createWebExtRunner } from '../web-ext'; +import { createWxtRunner } from '../wxt-runner'; import { ExtensionRunner } from '../../../types'; vi.mock('../../utils/wsl'); @@ -24,7 +25,16 @@ const createManualRunnerMock = vi.mocked(createManualRunner); vi.mock('../web-ext'); const createWebExtRunnerMock = vi.mocked(createWebExtRunner); +vi.mock('../wxt-runner'); +const createWxtRunnerMock = vi.mocked(createWxtRunner); + describe('createExtensionRunner', () => { + const originalDisplay = process.env.DISPLAY; + + afterEach(() => { + process.env.DISPLAY = originalDisplay; + }); + it('should return a Safari runner when browser is "safari"', async () => { setFakeWxt({ config: { @@ -37,7 +47,8 @@ describe('createExtensionRunner', () => { await expect(createExtensionRunner()).resolves.toBe(safariRunner); }); - it('should return a WSL runner when `is-wsl` is true', async () => { + it('should return a WSL runner when `is-wsl` is true and DISPLAY is not :0', async () => { + process.env.DISPLAY = ':1'; isWslMock.mockResolvedValueOnce(true); setFakeWxt({ config: { @@ -50,8 +61,36 @@ describe('createExtensionRunner', () => { await expect(createExtensionRunner()).resolves.toBe(wslRunner); }); + it('should return an internal runner when `is-wsl` is true and DISPLAY is :0 for Chrome/Chromium', async () => { + process.env.DISPLAY = ':0'; + isWslMock.mockResolvedValueOnce(true); + setFakeWxt({ + config: { + browser: 'chrome', + }, + }); + const internalRunner = mock(); + createWxtRunnerMock.mockReturnValue(internalRunner); + + await expect(createExtensionRunner()).resolves.toBe(internalRunner); + }); + + it('should return an internal runner when `is-wsl` is true and DISPLAY is :0 for Firefox', async () => { + process.env.DISPLAY = ':0'; + isWslMock.mockResolvedValueOnce(true); + setFakeWxt({ + config: { + browser: 'firefox', + }, + }); + const internalRunner = mock(); + createWxtRunnerMock.mockReturnValue(internalRunner); + + await expect(createExtensionRunner()).resolves.toBe(internalRunner); + }); + it('should return a manual runner when `runner.disabled` is true', async () => { - isWslMock.mockResolvedValueOnce(false); + isWslMock.mockResolvedValueOnce(true); setFakeWxt({ config: { browser: 'chrome', @@ -69,6 +108,7 @@ describe('createExtensionRunner', () => { }); it('should return a web-ext runner otherwise', async () => { + isWslMock.mockResolvedValueOnce(false); setFakeWxt({ config: { browser: 'chrome', diff --git a/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts b/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts new file mode 100644 index 000000000..f85d8ca61 --- /dev/null +++ b/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts @@ -0,0 +1,117 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createWebExtRunner } from '../web-ext'; +import { setFakeWxt } from '../../utils/testing/fake-objects'; +import { isWsl } from '../../utils/wsl'; + +vi.mock('node:fs/promises', () => ({ + access: vi.fn(async (filePath: string) => { + if (filePath === '/opt/google/chrome/chrome') return; + throw new Error('ENOENT'); + }), + realpath: vi.fn(async (filePath: string) => filePath), +})); + +vi.mock('../../utils/wsl'); +const isWslMock = vi.mocked(isWsl); + +const cmdRun = vi.fn(); + +vi.mock('web-ext-run', () => ({ + default: { + cmd: { + run: cmdRun, + }, + }, +})); + +vi.mock('web-ext-run/util/logger', () => ({ + consoleStream: { + write: vi.fn(), + }, +})); + +describe('createWebExtRunner (WSLg)', () => { + const originalDisplay = process.env.DISPLAY; + + afterEach(() => { + process.env.DISPLAY = originalDisplay; + cmdRun.mockReset(); + }); + + it('should ignore Windows-style binaries on WSLg so CDP pipes can work', async () => { + process.env.DISPLAY = ':0'; + isWslMock.mockResolvedValueOnce(true); + + cmdRun.mockResolvedValueOnce({ + exit: vi.fn(), + }); + + setFakeWxt({ + config: { + browser: 'chrome', + manifestVersion: 3, + outDir: '/tmp/wxt-out', + runnerConfig: { + config: { + binaries: { + chrome: + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + }, + }, + }, + }, + }); + + const runner = createWebExtRunner(); + await runner.openBrowser(); + + expect(cmdRun).toHaveBeenCalledTimes(1); + const finalConfig = cmdRun.mock.calls[0][0] as Record; + expect(finalConfig.chromiumBinary).toBe('/opt/google/chrome/chrome'); + }); + + it('should coerce chromiumArgs --user-data-dir into chromiumProfile + keepProfileChanges on WSLg', async () => { + process.env.DISPLAY = ':0'; + isWslMock.mockResolvedValueOnce(true); + + cmdRun.mockResolvedValueOnce({ + exit: vi.fn(), + }); + + setFakeWxt({ + config: { + root: '/home/user/project', + browser: 'chrome', + manifestVersion: 3, + outDir: '/tmp/wxt-out', + runnerConfig: { + config: { + chromiumArgs: [ + '--user-data-dir=./.wxt/chrome-data', + '--some-other-flag', + ], + }, + }, + }, + }); + + const runner = createWebExtRunner(); + await runner.openBrowser(); + + expect(cmdRun).toHaveBeenCalledTimes(1); + const finalConfig = cmdRun.mock.calls[0][0] as Record; + expect(finalConfig.chromiumProfile).toBe( + '/home/user/project/.wxt/chrome-data', + ); + expect(finalConfig.keepProfileChanges).toBe(true); + expect(finalConfig.args).toEqual( + expect.arrayContaining([ + '--unsafely-disable-devtools-self-xss-warnings', + '--some-other-flag', + ]), + ); + expect(finalConfig.args).not.toEqual( + expect.arrayContaining([expect.stringMatching(/--user-data-dir/)]), + ); + }); +}); diff --git a/packages/wxt/src/core/runners/__tests__/wsl.test.ts b/packages/wxt/src/core/runners/__tests__/wsl.test.ts new file mode 100644 index 000000000..22c4c5bf6 --- /dev/null +++ b/packages/wxt/src/core/runners/__tests__/wsl.test.ts @@ -0,0 +1,26 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createWslRunner } from '../wsl'; +import { setFakeWxt } from '../../utils/testing/fake-objects'; + +describe('createWslRunner', () => { + const originalDisplay = process.env.DISPLAY; + + afterEach(() => { + process.env.DISPLAY = originalDisplay; + }); + + it('should warn when running in WSL without WSLg', async () => { + process.env.DISPLAY = ':0'; + const fake = setFakeWxt({ + config: { + browser: 'chrome', + outDir: '/tmp/wxt-out', + }, + }); + + const runner = createWslRunner(); + await runner.openBrowser(); + + expect(fake.logger.warn).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/wxt/src/core/runners/index.ts b/packages/wxt/src/core/runners/index.ts index 65c4523f6..7b56028a7 100644 --- a/packages/wxt/src/core/runners/index.ts +++ b/packages/wxt/src/core/runners/index.ts @@ -3,14 +3,31 @@ import { createWslRunner } from './wsl'; import { createWebExtRunner } from './web-ext'; import { createSafariRunner } from './safari'; import { createManualRunner } from './manual'; +import { createWxtRunner } from './wxt-runner'; import { isWsl } from '../utils/wsl'; import { wxt } from '../wxt'; +import { KNOWN_BROWSER_PATHS, type KnownTarget } from '@wxt-dev/runner'; + +const KNOWN_TARGETS = new Set(Object.keys(KNOWN_BROWSER_PATHS)); +function isKnownTarget(browser: string): browser is KnownTarget { + return KNOWN_TARGETS.has(browser); +} export async function createExtensionRunner(): Promise { if (wxt.config.browser === 'safari') return createSafariRunner(); - if (await isWsl()) return createWslRunner(); if (wxt.config.runnerConfig.config?.disabled) return createManualRunner(); + const runningInWsl = await isWsl(); + const isWslg = process.env.DISPLAY === ':0'; + if (runningInWsl && !isWslg) return createWslRunner(); + + // On WSLg, prefer WXT's own runner for browsers supported by @wxt-dev/runner. + // This avoids web-ext-run / chrome-launcher WSL path rewriting (e.g. \\wsl.localhost\...) + // and keeps temp/profile directories on the Linux filesystem. + if (runningInWsl && isWslg && isKnownTarget(wxt.config.browser)) { + return createWxtRunner(); + } + return createWebExtRunner(); } diff --git a/packages/wxt/src/core/runners/web-ext.ts b/packages/wxt/src/core/runners/web-ext.ts index f7efcdbe9..8809deb8b 100644 --- a/packages/wxt/src/core/runners/web-ext.ts +++ b/packages/wxt/src/core/runners/web-ext.ts @@ -3,6 +3,10 @@ import { ExtensionRunner } from '../../types'; import { formatDuration } from '../utils/time'; import defu from 'defu'; import { wxt } from '../wxt'; +import { isWsl } from '../utils/wsl'; +import * as fs from 'node:fs/promises'; +import { constants as fsConstants } from 'node:fs'; +import path from 'node:path'; /** * Create an `ExtensionRunner` backed by `web-ext`. @@ -31,32 +35,124 @@ export function createWebExtRunner(): ExtensionRunner { webExtLogger.consoleStream.write = ({ level, msg, name }) => { if (level >= ERROR_LOG_LEVEL) wxt.logger.error(name, msg); if (level >= WARN_LOG_LEVEL) wxt.logger.warn(msg); + if (level < WARN_LOG_LEVEL) wxt.logger.debug(name, msg); }; const wxtUserConfig = wxt.config.runnerConfig.config; + + const runningInWsl = await isWsl(); + const runningInWslg = runningInWsl && process.env.DISPLAY === ':0'; + const sanitizePathForWslg = ( + value: string | undefined, + label: string, + ) => { + if (!runningInWslg || value == null) return value; + if (isWindowsPath(value)) { + wxt.logger.warn( + `[web-ext] Ignoring ${label}="${value}" on WSLg (DISPLAY=:0). Windows paths/binaries are incompatible with the CDP pipe used to load extensions. Install a Linux browser in WSL and omit this setting.`, + ); + return undefined; + } + return value; + }; + + const chromiumBinaryFromConfig = + wxt.config.browser === 'firefox' + ? undefined + : sanitizePathForWslg( + wxtUserConfig?.binaries?.[wxt.config.browser], + `binaries.${wxt.config.browser}`, + ); + const chromiumBinary = await resolveChromiumBinaryForRemoteDebuggingPipe({ + chromiumBinary: chromiumBinaryFromConfig, + runningInWslg, + }); + + const chromiumUserDataDirOverride = + wxt.config.browser === 'firefox' + ? undefined + : extractUserDataDirFromChromiumArgs(wxtUserConfig?.chromiumArgs); + const shouldCoerceUserDataDirToProfile = + runningInWslg && + wxt.config.browser !== 'firefox' && + wxtUserConfig?.chromiumProfile == null && + wxtUserConfig?.keepProfileChanges == null && + chromiumUserDataDirOverride != null; + + const coercedChromiumProfile = shouldCoerceUserDataDirToProfile + ? sanitizePathForWslg( + resolveChromiumProfilePath( + wxt.config.root, + chromiumUserDataDirOverride, + ), + 'chromiumProfile', + ) + : sanitizePathForWslg( + wxtUserConfig?.chromiumProfile, + 'chromiumProfile', + ); + + const coercedKeepProfileChanges = + wxt.config.browser === 'firefox' + ? wxtUserConfig?.keepProfileChanges + : // Match the Windows docs behavior when a profile directory is used. + // This prevents web-ext-run from creating a brand new temp profile on every launch. + (wxtUserConfig?.keepProfileChanges ?? + (runningInWslg && coercedChromiumProfile != null + ? true + : undefined)); + + const coercedChromiumArgs = + wxt.config.browser === 'firefox' + ? wxtUserConfig?.chromiumArgs + : shouldCoerceUserDataDirToProfile + ? removeUserDataDirFromChromiumArgs(wxtUserConfig?.chromiumArgs) + : wxtUserConfig?.chromiumArgs; + + if (shouldCoerceUserDataDirToProfile) { + wxt.logger.warn( + `[web-ext] On WSLg (DISPLAY=:0), converting chromiumArgs "--user-data-dir" into { chromiumProfile, keepProfileChanges: true } (Windows-style) to avoid creating throwaway profiles on each launch.`, + ); + } else if ( + runningInWslg && + wxt.config.browser !== 'firefox' && + wxtUserConfig?.chromiumProfile != null && + wxtUserConfig?.keepProfileChanges == null + ) { + wxt.logger.warn( + `[web-ext] On WSLg (DISPLAY=:0), defaulting keepProfileChanges=true because chromiumProfile is set (Windows-style) to avoid creating throwaway profiles on each launch.`, + ); + } + const userConfig = { browserConsole: wxtUserConfig?.openConsole, devtools: wxtUserConfig?.openDevtools, startUrl: wxtUserConfig?.startUrls, - keepProfileChanges: wxtUserConfig?.keepProfileChanges, + keepProfileChanges: coercedKeepProfileChanges, chromiumPort: wxtUserConfig?.chromiumPort, ...(wxt.config.browser === 'firefox' ? { - firefox: wxtUserConfig?.binaries?.firefox, - firefoxProfile: wxtUserConfig?.firefoxProfile, + firefox: sanitizePathForWslg( + wxtUserConfig?.binaries?.firefox, + 'binaries.firefox', + ), + firefoxProfile: sanitizePathForWslg( + wxtUserConfig?.firefoxProfile, + 'firefoxProfile', + ), prefs: wxtUserConfig?.firefoxPrefs, args: wxtUserConfig?.firefoxArgs, } : { - chromiumBinary: wxtUserConfig?.binaries?.[wxt.config.browser], - chromiumProfile: wxtUserConfig?.chromiumProfile, + chromiumBinary, + chromiumProfile: coercedChromiumProfile, chromiumPref: defu( wxtUserConfig?.chromiumPref, DEFAULT_CHROMIUM_PREFS, ), args: [ '--unsafely-disable-devtools-self-xss-warnings', - ...(wxtUserConfig?.chromiumArgs ?? []), + ...(coercedChromiumArgs ?? []), ], }), }; @@ -93,6 +189,134 @@ export function createWebExtRunner(): ExtensionRunner { }; } +async function resolveChromiumBinaryForRemoteDebuggingPipe({ + chromiumBinary, + runningInWslg, +}: { + chromiumBinary: string | undefined; + runningInWslg: boolean; +}): Promise { + if (!runningInWslg) return chromiumBinary; + + // On WSLg, Chrome's wrapper script (google-chrome / google-chrome-stable) + // uses bash process substitution which closes extra FDs on exec. + // That breaks Chrome's `--remote-debugging-pipe` mode and causes CDP to + // disconnect immediately. + // + // Prefer the actual Chrome binary to keep the CDP pipe open. + const googleChromeRealBinary = '/opt/google/chrome/chrome'; + const hasRealGoogleChrome = await isExecutable(googleChromeRealBinary); + + if (chromiumBinary == null) { + if (hasRealGoogleChrome) return googleChromeRealBinary; + return chromiumBinary; + } + + if (hasRealGoogleChrome && looksLikeGoogleChromeWrapper(chromiumBinary)) { + wxt.logger.warn( + `[web-ext] Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" on WSLg to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, + ); + return googleChromeRealBinary; + } + + // Handle cases where a wrapper was explicitly provided from a non-/opt path. + if (looksLikeGoogleChromeWrapper(chromiumBinary)) { + const resolved = await fs + .realpath(chromiumBinary) + .catch(() => chromiumBinary); + const sibling = path.join(path.dirname(resolved), 'chrome'); + if (await isExecutable(sibling)) return sibling; + } + + return chromiumBinary; +} + +async function isExecutable(filePath: string): Promise { + try { + await fs.access(filePath, fsConstants.X_OK); + return true; + } catch { + return false; + } +} + +function looksLikeGoogleChromeWrapper(filePath: string): boolean { + const base = path.basename(filePath); + if (base === 'google-chrome') return true; + if (base === 'google-chrome-stable') return true; + if (base === 'google-chrome-beta') return true; + if (base === 'google-chrome-dev') return true; + if (base === 'google-chrome-unstable') return true; + if (filePath === '/opt/google/chrome/google-chrome') return true; + return false; +} + +function isWindowsPath(value: string): boolean { + // Windows drive path: C:\... + if (/^[a-zA-Z]:\\/.test(value)) return true; + // WSL mounted drive: /mnt/c/... + if (/^\/mnt\/[a-zA-Z]\//.test(value)) return true; + // UNC-ish + if (value.startsWith('\\\\')) return true; + return false; +} + +function resolveChromiumProfilePath( + projectRoot: string, + userDataDir: string, +): string { + // If the user gave a relative path (common in Linux docs), make it absolute. + // This matches the Windows docs requirement and avoids depending on CWD. + return path.isAbsolute(userDataDir) + ? userDataDir + : path.resolve(projectRoot, userDataDir); +} + +function extractUserDataDirFromChromiumArgs( + chromiumArgs: string[] | undefined, +): string | undefined { + if (!chromiumArgs?.length) return undefined; + + for (let i = 0; i < chromiumArgs.length; i++) { + const arg = chromiumArgs[i]; + if (arg == null) continue; + + const prefix = '--user-data-dir='; + if (arg.startsWith(prefix)) return arg.slice(prefix.length); + + if (arg === '--user-data-dir') { + const next = chromiumArgs[i + 1]; + if (typeof next === 'string' && next.length > 0) return next; + return undefined; + } + } + + return undefined; +} + +function removeUserDataDirFromChromiumArgs( + chromiumArgs: string[] | undefined, +): string[] | undefined { + if (!chromiumArgs?.length) return chromiumArgs; + + const filtered: string[] = []; + for (let i = 0; i < chromiumArgs.length; i++) { + const arg = chromiumArgs[i]; + if (arg == null) continue; + + if (arg.startsWith('--user-data-dir=')) continue; + if (arg === '--user-data-dir') { + // Skip the value token too, if present. + i++; + continue; + } + + filtered.push(arg); + } + + return filtered; +} + // https://github.com/mozilla/web-ext/blob/e37e60a2738478f512f1255c537133321f301771/src/util/logger.js#L12 const WARN_LOG_LEVEL = 40; const ERROR_LOG_LEVEL = 50; diff --git a/packages/wxt/src/core/runners/wsl.ts b/packages/wxt/src/core/runners/wsl.ts index 4fc0b126f..3da1422a0 100644 --- a/packages/wxt/src/core/runners/wsl.ts +++ b/packages/wxt/src/core/runners/wsl.ts @@ -3,20 +3,22 @@ import { relative } from 'node:path'; import { wxt } from '../wxt'; /** - * The WSL runner just logs a warning message because `web-ext` doesn't work in WSL. + * WSL sometimes cannot launch browsers. + * + * Note: WSLg (DISPLAY=:0) is handled in `createExtensionRunner`. */ export function createWslRunner(): ExtensionRunner { return { async openBrowser() { wxt.logger.warn( - `Cannot open browser when using WSL. Load "${relative( + `Cannot auto-open browser when using WSL without WSLg (DISPLAY=:0). Load "${relative( process.cwd(), wxt.config.outDir, )}" as an unpacked extension manually`, ); }, async closeBrowser() { - // noop + // No-op }, }; } diff --git a/packages/wxt/src/core/runners/wxt-runner.ts b/packages/wxt/src/core/runners/wxt-runner.ts new file mode 100644 index 000000000..2e6299a9a --- /dev/null +++ b/packages/wxt/src/core/runners/wxt-runner.ts @@ -0,0 +1,254 @@ +import { ExtensionRunner } from '../../types'; +import { formatDuration } from '../utils/time'; +import { wxt } from '../wxt'; +import { + KNOWN_BROWSER_PATHS, + run, + type KnownTarget, + type Runner as WxtRunnerInstance, +} from '@wxt-dev/runner'; +import * as fs from 'node:fs/promises'; +import { constants as fsConstants } from 'node:fs'; +import path from 'node:path'; + +const KNOWN_TARGETS = new Set(Object.keys(KNOWN_BROWSER_PATHS)); +function isKnownTarget(value: string): value is KnownTarget { + return KNOWN_TARGETS.has(value); +} + +const FIREFOX_FAMILY_TARGETS = [ + 'firefox', + 'firefox-nightly', + 'firefox-developer-edition', + 'zen', +] as const satisfies readonly KnownTarget[]; +type FirefoxFamilyTarget = (typeof FIREFOX_FAMILY_TARGETS)[number]; + +function isFirefoxFamilyTarget( + target: KnownTarget, +): target is FirefoxFamilyTarget { + return (FIREFOX_FAMILY_TARGETS as readonly string[]).includes(target); +} + +export function createWxtRunner(): ExtensionRunner { + let runner: WxtRunnerInstance | undefined; + + return { + canOpen() { + return true; + }, + + async openBrowser() { + const startTime = Date.now(); + + const userConfig = wxt.config.runnerConfig.config; + const browser = wxt.config.browser; + + if (!isKnownTarget(browser)) { + throw Error( + `Internal runner does not support browser="${browser}". Use a Chromium/Firefox family browser, or disable the runner with runnerConfig.config.disabled=true.`, + ); + } + + if (isFirefoxFamilyTarget(browser) && wxt.config.manifestVersion === 3) { + throw Error( + 'Dev mode does not support Firefox MV3. For alternatives, see https://github.com/wxt-dev/wxt/issues/230#issuecomment-1806881653', + ); + } + + const runningInWslg = process.env.DISPLAY === ':0'; + + const binaryFromConfig = sanitizePathForWslg( + userConfig?.binaries?.[browser], + `binaries.${browser}`, + runningInWslg, + ); + + const browserBinaryOverride = !isFirefoxFamilyTarget(browser) + ? await resolveChromiumBinaryForRemoteDebuggingPipe({ + chromiumBinary: binaryFromConfig, + runningInWslg, + }) + : binaryFromConfig; + + const startUrls = Array.isArray(userConfig?.startUrls) + ? userConfig.startUrls + : undefined; + + if (isFirefoxFamilyTarget(browser)) { + const firefoxArgs: string[] = [...(userConfig?.firefoxArgs ?? [])]; + if (startUrls) firefoxArgs.push(...startUrls); + + const firefoxProfile = sanitizePathForWslg( + userConfig?.firefoxProfile, + 'firefoxProfile', + runningInWslg, + ); + + const dataPersistence = + firefoxProfile != null || userConfig?.keepProfileChanges + ? 'project' + : 'none'; + const projectDataDir = + firefoxProfile != null + ? resolveProfilePath(wxt.config.root, firefoxProfile) + : undefined; + + const runOptions = { + target: browser, + extensionDir: wxt.config.outDir, + firefoxArgs, + dataPersistence, + projectDataDir, + } as Parameters[0]; + + if (browserBinaryOverride) { + runOptions.browserBinaries = { + [browser]: browserBinaryOverride, + }; + } + + runner = await run(runOptions); + } else { + const chromiumArgs: string[] = [ + '--unsafely-disable-devtools-self-xss-warnings', + ...(userConfig?.chromiumArgs ?? []), + ]; + + if (userConfig?.openDevtools) { + chromiumArgs.push('--auto-open-devtools-for-tabs'); + } + + if (startUrls) { + chromiumArgs.push(...startUrls); + } + + const chromiumProfile = sanitizePathForWslg( + userConfig?.chromiumProfile, + 'chromiumProfile', + runningInWslg, + ); + + const dataPersistence = + chromiumProfile != null || userConfig?.keepProfileChanges + ? 'project' + : 'none'; + const projectDataDir = + chromiumProfile != null + ? resolveProfilePath(wxt.config.root, chromiumProfile) + : undefined; + + const runOptions = { + target: browser, + extensionDir: wxt.config.outDir, + chromiumArgs, + chromiumRemoteDebuggingPort: userConfig?.chromiumPort, + dataPersistence, + projectDataDir, + } as Parameters[0]; + + if (browserBinaryOverride) { + runOptions.browserBinaries = { + [browser]: browserBinaryOverride, + }; + } + + runner = await run(runOptions); + } + + const duration = Date.now() - startTime; + wxt.logger.success(`Opened browser in ${formatDuration(duration)}`); + }, + + async closeBrowser() { + runner?.stop(); + runner = undefined; + }, + }; +} + +function sanitizePathForWslg( + value: string | undefined, + label: string, + runningInWslg: boolean, +): string | undefined { + if (!runningInWslg || value == null) return value; + if (isWindowsPath(value)) { + wxt.logger.warn( + `[runner] Ignoring ${label}="${value}" on WSLg (DISPLAY=:0). Windows paths/binaries are incompatible with CDP pipe extension install. Install a Linux browser in WSL and omit this setting.`, + ); + return undefined; + } + return value; +} + +function isWindowsPath(value: string): boolean { + // Windows drive path: C:\... + if (/^[a-zA-Z]:\\/.test(value)) return true; + // WSL mounted drive: /mnt/c/... + if (/^\/mnt\/[a-zA-Z]\//.test(value)) return true; + // UNC-ish + if (value.startsWith('\\\\')) return true; + return false; +} + +async function resolveChromiumBinaryForRemoteDebuggingPipe({ + chromiumBinary, + runningInWslg, +}: { + chromiumBinary: string | undefined; + runningInWslg: boolean; +}): Promise { + if (!runningInWslg) return chromiumBinary; + + const googleChromeRealBinary = '/opt/google/chrome/chrome'; + const hasRealGoogleChrome = await isExecutable(googleChromeRealBinary); + + if (chromiumBinary == null) { + if (hasRealGoogleChrome) return googleChromeRealBinary; + return chromiumBinary; + } + + if (hasRealGoogleChrome && looksLikeGoogleChromeWrapper(chromiumBinary)) { + wxt.logger.warn( + `[runner] Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" on WSLg to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, + ); + return googleChromeRealBinary; + } + + if (looksLikeGoogleChromeWrapper(chromiumBinary)) { + const resolved = await fs + .realpath(chromiumBinary) + .catch(() => chromiumBinary); + const sibling = path.join(path.dirname(resolved), 'chrome'); + if (await isExecutable(sibling)) return sibling; + } + + return chromiumBinary; +} + +async function isExecutable(filePath: string): Promise { + try { + await fs.access(filePath, fsConstants.X_OK); + return true; + } catch { + return false; + } +} + +function looksLikeGoogleChromeWrapper(filePath: string): boolean { + const base = path.basename(filePath); + if (base === 'google-chrome') return true; + if (base === 'google-chrome-stable') return true; + if (base === 'google-chrome-beta') return true; + if (base === 'google-chrome-dev') return true; + if (base === 'google-chrome-unstable') return true; + if (filePath === '/opt/google/chrome/google-chrome') return true; + return false; +} + +function resolveProfilePath(projectRoot: string, profileDir: string): string { + return path.isAbsolute(profileDir) + ? profileDir + : path.resolve(projectRoot, profileDir); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83ab6336f..7c70d2890 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,7 +75,7 @@ importers: version: 2.13.1 tsdown: specifier: ^0.18.1 - version: 0.18.1(publint@0.3.16)(typescript@5.9.3) + version: 0.18.4(publint@0.3.16)(typescript@5.9.3) tsx: specifier: 4.21.0 version: 4.21.0 @@ -407,6 +407,9 @@ importers: '@wxt-dev/browser': specifier: workspace:^ version: link:../browser + '@wxt-dev/runner': + specifier: workspace:^ + version: link:../runner '@wxt-dev/storage': specifier: workspace:^1.0.0 version: link:../storage @@ -736,8 +739,8 @@ packages: resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -801,6 +804,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} @@ -843,6 +851,10 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -891,8 +903,8 @@ packages: search-insights: optional: true - '@emnapi/core@1.7.1': - resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} @@ -1384,8 +1396,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@napi-rs/wasm-runtime@1.1.0': - resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1402,6 +1414,9 @@ packages: '@oxc-project/types@0.103.0': resolution: {integrity: sha512-bkiYX5kaXWwUessFRSoXFkGIQTmc6dLGdxuRTrC+h8PSnIdZyuXHHlLAeTmOue5Br/a0/a7dHH0Gca6eXn9MKg==} + '@oxc-project/types@0.110.0': + resolution: {integrity: sha512-6Ct21OIlrEnFEJk5LT4e63pk3btsI6/TusD/GStLi7wYlGJNOl1GI9qvXAnRAxQU9zqA2Oz+UwhfTOU2rPZVow==} + '@oxlint/darwin-arm64@1.33.0': resolution: {integrity: sha512-PmEQDLHAxiAdyttQ1ZWXd+5VpHLbHf3FTMJL9bg5TZamDnhNiW/v0Pamv3MTAdymnoDI3H8IVLAN/SAseV/adw==} cpu: [arm64] @@ -1549,79 +1564,156 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - '@rolldown/binding-android-arm64@1.0.0-beta.55': - resolution: {integrity: sha512-5cPpHdO+zp+klznZnIHRO1bMHDq5hS9cqXodEKAaa/dQTPDjnE91OwAsy3o1gT2x4QaY8NzdBXAvutYdaw0WeA==} + '@rolldown/binding-android-arm64@1.0.0-beta.57': + resolution: {integrity: sha512-GoOVDy8bjw9z1K30Oo803nSzXJS/vWhFijFsW3kzvZCO8IZwFnNa6pGctmbbJstKl3Fv6UBwyjJQN6msejW0IQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-beta.55': - resolution: {integrity: sha512-l0887CGU2SXZr0UJmeEcXSvtDCOhDTTYXuoWbhrEJ58YQhQk24EVhDhHMTyjJb1PBRniUgNc1G0T51eF8z+TWw==} + '@rolldown/binding-android-arm64@1.0.0-rc.1': + resolution: {integrity: sha512-He6ZoCfv5D7dlRbrhNBkuMVIHd0GDnjJwbICE1OWpG7G3S2gmJ+eXkcNLJjzjNDpeI2aRy56ou39AJM9AD8YFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-beta.57': + resolution: {integrity: sha512-9c4FOhRGpl+PX7zBK5p17c5efpF9aSpTPgyigv57hXf5NjQUaJOOiejPLAtFiKNBIfm5Uu6yFkvLKzOafNvlTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.55': - resolution: {integrity: sha512-d7qP2AVYzN0tYIP4vJ7nmr26xvmlwdkLD/jWIc9Z9dqh5y0UGPigO3m5eHoHq9BNazmwdD9WzDHbQZyXFZjgtA==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.1': + resolution: {integrity: sha512-YzJdn08kSOXnj85ghHauH2iHpOJ6eSmstdRTLyaziDcUxe9SyQJgGyx/5jDIhDvtOcNvMm2Ju7m19+S/Rm1jFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.57': + resolution: {integrity: sha512-6RsB8Qy4LnGqNGJJC/8uWeLWGOvbRL/KG5aJ8XXpSEupg/KQtlBEiFaYU/Ma5Usj1s+bt3ItkqZYAI50kSplBA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.55': - resolution: {integrity: sha512-j311E4NOB0VMmXHoDDZhrWidUf7L/Sa6bu/+i2cskvHKU40zcUNPSYeD2YiO2MX+hhDFa5bJwhliYfs+bTrSZw==} + '@rolldown/binding-darwin-x64@1.0.0-rc.1': + resolution: {integrity: sha512-cIvAbqM+ZVV6lBSKSBtlNqH5iCiW933t1q8j0H66B3sjbe8AxIRetVqfGgcHcJtMzBIkIALlL9fcDrElWLJQcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.57': + resolution: {integrity: sha512-uA9kG7+MYkHTbqwv67Tx+5GV5YcKd33HCJIi0311iYBd25yuwyIqvJfBdt1VVB8tdOlyTb9cPAgfCki8nhwTQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.55': - resolution: {integrity: sha512-lAsaYWhfNTW2A/9O7zCpb5eIJBrFeNEatOS/DDOZ5V/95NHy50g4b/5ViCqchfyFqRb7MKUR18/+xWkIcDkeIw==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.1': + resolution: {integrity: sha512-rVt+B1B/qmKwCl1XD02wKfgh3vQPXRXdB/TicV2w6g7RVAM1+cZcpigwhLarqiVCxDObFZ7UgXCxPC7tpDoRog==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.57': + resolution: {integrity: sha512-3KkS0cHsllT2T+Te+VZMKHNw6FPQihYsQh+8J4jkzwgvAQpbsbXmrqhkw3YU/QGRrD8qgcOvBr6z5y6Jid+rmw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': + resolution: {integrity: sha512-69YKwJJBOFprQa1GktPgbuBOfnn+EGxu8sBJ1TjPER+zhSpYeaU4N07uqmyBiksOLGXsMegymuecLobfz03h8Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.55': - resolution: {integrity: sha512-2x6ffiVLZrQv7Xii9+JdtyT1U3bQhKj59K3eRnYlrXsKyjkjfmiDUVx2n+zSyijisUqD62fcegmx2oLLfeTkCA==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.57': + resolution: {integrity: sha512-A3/wu1RgsHhqP3rVH2+sM81bpk+Qd2XaHTl8LtX5/1LNR7QVBFBCpAoiXwjTdGnI5cMdBVi7Z1pi52euW760Fw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.55': - resolution: {integrity: sha512-QbNncvqAXziya5wleI+OJvmceEE15vE4yn4qfbI/hwT/+8ZcqxyfRZOOh62KjisXxp4D0h3JZspycXYejxAU3w==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': + resolution: {integrity: sha512-9JDhHUf3WcLfnViFWm+TyorqUtnSAHaCzlSNmMOq824prVuuzDOK91K0Hl8DUcEb9M5x2O+d2/jmBMsetRIn3g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.55': - resolution: {integrity: sha512-YZCTZZM+rujxwVc6A+QZaNMJXVtmabmFYLG2VGQTKaBfYGvBKUgtbMEttnp/oZ88BMi2DzadBVhOmfQV8SuHhw==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.57': + resolution: {integrity: sha512-d0kIVezTQtazpyWjiJIn5to8JlwfKITDqwsFv0Xc6s31N16CD2PC/Pl2OtKgS7n8WLOJbfqgIp5ixYzTAxCqMg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': + resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.57': + resolution: {integrity: sha512-E199LPijo98yrLjPCmETx8EF43sZf9t3guSrLee/ej1rCCc3zDVTR4xFfN9BRAapGVl7/8hYqbbiQPTkv73kUg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.55': - resolution: {integrity: sha512-28q9OQ/DDpFh2keS4BVAlc3N65/wiqKbk5K1pgLdu/uWbKa8hgUJofhXxqO+a+Ya2HVTUuYHneWsI2u+eu3N5Q==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': + resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.55': - resolution: {integrity: sha512-LiCA4BjCnm49B+j1lFzUtlC+4ZphBv0d0g5VqrEJua/uyv9Ey1v9tiaMql1C8c0TVSNDUmrkfHQ71vuQC7YfpQ==} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.57': + resolution: {integrity: sha512-++EQDpk/UJ33kY/BNsh7A7/P1sr/jbMuQ8cE554ZIy+tCUWCivo9zfyjDUoiMdnxqX6HLJEqqGnbGQOvzm2OMQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': + resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.57': + resolution: {integrity: sha512-voDEBcNqxbUv/GeXKFtxXVWA+H45P/8Dec4Ii/SbyJyGvCqV1j+nNHfnFUIiRQ2Q40DwPe/djvgYBs9PpETiMA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.55': - resolution: {integrity: sha512-nZ76tY7T0Oe8vamz5Cv5CBJvrqeQxwj1WaJ2GxX8Msqs0zsQMMcvoyxOf0glnJlxxgKjtoBxAOxaAU8ERbW6Tg==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': + resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.57': + resolution: {integrity: sha512-bRhcF7NLlCnpkzLVlVhrDEd0KH22VbTPkPTbMjlYvqhSmarxNIq5vtlQS8qmV7LkPKHrNLWyJW/V/sOyFba26Q==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.55': - resolution: {integrity: sha512-TFVVfLfhL1G+pWspYAgPK/FSqjiBtRKYX9hixfs508QVEZPQlubYAepHPA7kEa6lZXYj5ntzF87KC6RNhxo+ew==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': + resolution: {integrity: sha512-2mOxY562ihHlz9lEXuaGEIDCZ1vI+zyFdtsoa3M62xsEunDXQE+DVPO4S4x5MPK9tKulG/aFcA/IH5eVN257Cw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.57': + resolution: {integrity: sha512-rnDVGRks2FQ2hgJ2g15pHtfxqkGFGjJQUDWzYznEkE8Ra2+Vag9OffxdbJMZqBWXHVM0iS4dv8qSiEn7bO+n1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': + resolution: {integrity: sha512-oQVOP5cfAWZwRD0Q3nGn/cA9FW3KhMMuQ0NIndALAe6obqjLhqYVYDiGGRGrxvnjJsVbpLwR14gIUYnpIcHR1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.55': - resolution: {integrity: sha512-j1WBlk0p+ISgLzMIgl0xHp1aBGXenoK2+qWYc/wil2Vse7kVOdFq9aeQ8ahK6/oxX2teQ5+eDvgjdywqTL+daA==} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.57': + resolution: {integrity: sha512-OqIUyNid1M4xTj6VRXp/Lht/qIP8fo25QyAZlCP+p6D2ATCEhyW4ZIFLnC9zAGN/HMbXoCzvwfa8Jjg/8J4YEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': + resolution: {integrity: sha512-Ydsxxx++FNOuov3wCBPaYjZrEvKOOGq3k+BF4BPridhg2pENfitSRD2TEuQ8i33bp5VptuNdC9IzxRKU031z5A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1632,8 +1724,11 @@ packages: '@rolldown/pluginutils@1.0.0-beta.34': resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==} - '@rolldown/pluginutils@1.0.0-beta.55': - resolution: {integrity: sha512-vajw/B3qoi7aYnnD4BQ4VoCcXQWnF0roSwE2iynbNxgW4l9mFwtLmLmUhpDdcTBfKyZm1p/T0D13qG94XBLohA==} + '@rolldown/pluginutils@1.0.0-beta.57': + resolution: {integrity: sha512-aQNelgx14tGA+n2tNSa9x6/jeoCL9fkDeCei7nOKnHx0fEFRRMu5ReiITo+zZD5TzWDGGRjbSYCs93IfRIyTuQ==} + + '@rolldown/pluginutils@1.0.0-rc.1': + resolution: {integrity: sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA==} '@rollup/rollup-android-arm-eabi@4.50.0': resolution: {integrity: sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==} @@ -2895,6 +2990,9 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hookable@6.0.1: + resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} + html-entities@2.3.3: resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} @@ -2926,8 +3024,8 @@ packages: import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - import-without-cache@0.2.4: - resolution: {integrity: sha512-b/Ke0y4n26ffQhkLvgBxV/NVO/QEE6AZlrMj8DYuxBWNAAu4iMQWZTFWzKcCTEmv7VQ0ae0j8KwrlGzSy8sYQQ==} + import-without-cache@0.2.5: + resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==} engines: {node: '>=20.19.0'} inherits@2.0.4: @@ -3794,15 +3892,15 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rolldown-plugin-dts@0.19.1: - resolution: {integrity: sha512-6z501zDTGq6ZrIEdk57qNUwq7kBRGzv3I3SAN2HMJ2KFYjHLnAuPYOmvfiwdxbRZMJ0iMdkV9rYdC3GjurT2cg==} + rolldown-plugin-dts@0.20.0: + resolution: {integrity: sha512-cLAY1kN2ilTYMfZcFlGWbXnu6Nb+8uwUBsi+Mjbh4uIx7IN8uMOmJ7RxrrRgPsO4H7eSz3E+JwGoL1gyugiyUA==} engines: {node: '>=20.19.0'} peerDependencies: '@ts-macro/tsc': ^0.3.6 '@typescript/native-preview': '>=7.0.0-dev.20250601.1' - rolldown: ^1.0.0-beta.55 + rolldown: ^1.0.0-beta.57 typescript: ^5.0.0 - vue-tsc: ~3.1.0 + vue-tsc: ~3.2.0 peerDependenciesMeta: '@ts-macro/tsc': optional: true @@ -3813,8 +3911,13 @@ packages: vue-tsc: optional: true - rolldown@1.0.0-beta.55: - resolution: {integrity: sha512-r8Ws43aYCnfO07ao0SvQRz4TBAtZJjGWNvScRBOHuiNHvjfECOJBIqJv0nUkL1GYcltjvvHswRilDF1ocsC0+g==} + rolldown@1.0.0-beta.57: + resolution: {integrity: sha512-lMMxcNN71GMsSko8RyeTaFoATHkCh4IWU7pYF73ziMYjhHZWfVesC6GQ+iaJCvZmVjvgSks9Ks1aaqEkBd8udg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rolldown@1.0.0-rc.1: + resolution: {integrity: sha512-M3AeZjYE6UclblEf531Hch0WfVC/NOL43Cc+WdF3J50kk5/fvouHhDumSGTh0oRjbZ8C4faaVr5r6Nx1xMqDGg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -4137,8 +4240,8 @@ packages: typescript: optional: true - tsdown@0.18.1: - resolution: {integrity: sha512-na4MdVA8QS9Zw++0KovGpjvw1BY5WvoCWcE4Aw0dyfff9nWK8BPzniQEVs+apGUg3DHaYMDfs+XiFaDDgqDDzQ==} + tsdown@0.18.4: + resolution: {integrity: sha512-J/tRS6hsZTkvqmt4+xdELUCkQYDuUCXgBv0fw3ImV09WPGbEKfsPD65E+WUjSu3E7Z6tji9XZ1iWs8rbGqB/ZA==} engines: {node: '>=20.19.0'} hasBin: true peerDependencies: @@ -4277,8 +4380,8 @@ packages: resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} engines: {node: '>=18.12.0'} - unrun@0.2.20: - resolution: {integrity: sha512-YhobStTk93HYRN/4iBs3q3/sd7knvju1XrzwwrVVfRujyTG1K88hGONIxCoJN0PWBuO+BX7fFiHH0sVDfE3MWw==} + unrun@0.2.26: + resolution: {integrity: sha512-A3DQLBcDyTui4Hlaoojkldg+8x+CIR+tcSHY0wzW+CgB4X/DNyH58jJpXp1B/EkE+yG6tU8iH1mWsLtwFU3IQg==} engines: {node: '>=20.19.0'} hasBin: true peerDependencies: @@ -4768,7 +4871,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.3.0 - tinyexec: 1.0.2 + tinyexec: 1.0.1 '@antfu/utils@9.2.0': {} @@ -4808,10 +4911,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.30 jsesc: 3.1.0 - '@babel/generator@7.28.5': + '@babel/generator@7.28.6': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -4873,6 +4976,10 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 @@ -4899,7 +5006,7 @@ snapshots: '@babel/traverse@7.27.7': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/generator': 7.28.3 '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/types': 7.28.5 @@ -4911,7 +5018,7 @@ snapshots: '@babel/traverse@7.28.3': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/generator': 7.28.3 '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.5 '@babel/template': 7.27.2 @@ -4930,6 +5037,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@1.0.2': {} '@commitlint/config-conventional@19.8.1': @@ -4980,7 +5092,7 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@emnapi/core@1.7.1': + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 @@ -5313,9 +5425,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@napi-rs/wasm-runtime@1.1.0': + '@napi-rs/wasm-runtime@1.1.1': dependencies: - '@emnapi/core': 1.7.1 + '@emnapi/core': 1.8.1 '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -5334,6 +5446,8 @@ snapshots: '@oxc-project/types@0.103.0': {} + '@oxc-project/types@0.110.0': {} + '@oxlint/darwin-arm64@1.33.0': optional: true @@ -5443,52 +5557,95 @@ snapshots: dependencies: quansync: 1.0.0 - '@rolldown/binding-android-arm64@1.0.0-beta.55': + '@rolldown/binding-android-arm64@1.0.0-beta.57': + optional: true + + '@rolldown/binding-android-arm64@1.0.0-rc.1': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.55': + '@rolldown/binding-darwin-arm64@1.0.0-beta.57': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.55': + '@rolldown/binding-darwin-arm64@1.0.0-rc.1': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.55': + '@rolldown/binding-darwin-x64@1.0.0-beta.57': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.55': + '@rolldown/binding-darwin-x64@1.0.0-rc.1': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.55': + '@rolldown/binding-freebsd-x64@1.0.0-beta.57': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.55': + '@rolldown/binding-freebsd-x64@1.0.0-rc.1': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.55': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.57': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.55': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.55': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.57': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.55': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.57': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.57': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.57': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.57': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.57': dependencies: - '@napi-rs/wasm-runtime': 1.1.0 + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.57': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.55': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.55': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.57': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': optional: true '@rolldown/pluginutils@1.0.0-beta.29': {} '@rolldown/pluginutils@1.0.0-beta.34': {} - '@rolldown/pluginutils@1.0.0-beta.55': {} + '@rolldown/pluginutils@1.0.0-beta.57': {} + + '@rolldown/pluginutils@1.0.0-rc.1': {} '@rollup/rollup-android-arm-eabi@4.50.0': optional: true @@ -6869,6 +7026,8 @@ snapshots: hookable@5.5.3: {} + hookable@6.0.1: {} + html-entities@2.3.3: {} html-escaper@2.0.2: {} @@ -6894,7 +7053,7 @@ snapshots: import-meta-resolve@4.2.0: {} - import-without-cache@0.2.4: {} + import-without-cache@0.2.5: {} inherits@2.0.4: {} @@ -7887,9 +8046,9 @@ snapshots: rfdc@1.4.1: {} - rolldown-plugin-dts@0.19.1(rolldown@1.0.0-beta.55)(typescript@5.9.3): + rolldown-plugin-dts@0.20.0(rolldown@1.0.0-beta.57)(typescript@5.9.3): dependencies: - '@babel/generator': 7.28.5 + '@babel/generator': 7.28.6 '@babel/parser': 7.28.5 '@babel/types': 7.28.5 ast-kit: 2.2.0 @@ -7897,30 +8056,49 @@ snapshots: dts-resolver: 2.1.3 get-tsconfig: 4.13.0 obug: 2.1.1 - rolldown: 1.0.0-beta.55 + rolldown: 1.0.0-beta.57 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - oxc-resolver - rolldown@1.0.0-beta.55: + rolldown@1.0.0-beta.57: dependencies: '@oxc-project/types': 0.103.0 - '@rolldown/pluginutils': 1.0.0-beta.55 + '@rolldown/pluginutils': 1.0.0-beta.57 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.57 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.57 + '@rolldown/binding-darwin-x64': 1.0.0-beta.57 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.57 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.57 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.57 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.57 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.57 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.57 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.57 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.57 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.57 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.57 + + rolldown@1.0.0-rc.1: + dependencies: + '@oxc-project/types': 0.110.0 + '@rolldown/pluginutils': 1.0.0-rc.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.55 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.55 - '@rolldown/binding-darwin-x64': 1.0.0-beta.55 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.55 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.55 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.55 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.55 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.55 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.55 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.55 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.55 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.55 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.55 + '@rolldown/binding-android-arm64': 1.0.0-rc.1 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.1 + '@rolldown/binding-darwin-x64': 1.0.0-rc.1 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.1 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.1 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.1 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.1 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.1 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.1 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.1 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.1 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.1 rollup@4.50.0: dependencies: @@ -8265,24 +8443,24 @@ snapshots: optionalDependencies: typescript: 5.9.3 - tsdown@0.18.1(publint@0.3.16)(typescript@5.9.3): + tsdown@0.18.4(publint@0.3.16)(typescript@5.9.3): dependencies: ansis: 4.2.0 cac: 6.7.14 defu: 6.1.4 empathic: 2.0.0 - hookable: 5.5.3 - import-without-cache: 0.2.4 + hookable: 6.0.1 + import-without-cache: 0.2.5 obug: 2.1.1 picomatch: 4.0.3 - rolldown: 1.0.0-beta.55 - rolldown-plugin-dts: 0.19.1(rolldown@1.0.0-beta.55)(typescript@5.9.3) + rolldown: 1.0.0-beta.57 + rolldown-plugin-dts: 0.20.0(rolldown@1.0.0-beta.57)(typescript@5.9.3) semver: 7.7.3 tinyexec: 1.0.2 tinyglobby: 0.2.15 tree-kill: 1.2.2 unconfig-core: 7.4.2 - unrun: 0.2.20 + unrun: 0.2.26 optionalDependencies: publint: 0.3.16 typescript: 5.9.3 @@ -8438,9 +8616,9 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 - unrun@0.2.20: + unrun@0.2.26: dependencies: - rolldown: 1.0.0-beta.55 + rolldown: 1.0.0-rc.1 update-browserslist-db@1.1.3(browserslist@4.25.4): dependencies: From 8193bd2391f4d0d52c3aff2bb88a7ee8d94d9524 Mon Sep 17 00:00:00 2001 From: Seismix Date: Mon, 26 Jan 2026 14:53:40 +0100 Subject: [PATCH 2/7] refactor: improve WSL GUI detection to support both X11 and Wayland --- .../src/core/runners/__tests__/index.test.ts | 37 ++++- .../runners/__tests__/web-ext.wsl.test.ts | 146 ++++++++++++++++-- .../src/core/runners/__tests__/wsl.test.ts | 16 +- packages/wxt/src/core/runners/index.ts | 15 +- packages/wxt/src/core/runners/web-ext.ts | 30 ++-- packages/wxt/src/core/runners/wsl.ts | 6 +- packages/wxt/src/core/runners/wxt-runner.ts | 33 ++-- packages/wxt/src/core/utils/wsl.ts | 13 ++ 8 files changed, 222 insertions(+), 74 deletions(-) diff --git a/packages/wxt/src/core/runners/__tests__/index.test.ts b/packages/wxt/src/core/runners/__tests__/index.test.ts index 496bf0478..eb8d71baf 100644 --- a/packages/wxt/src/core/runners/__tests__/index.test.ts +++ b/packages/wxt/src/core/runners/__tests__/index.test.ts @@ -1,17 +1,18 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createExtensionRunner } from '..'; import { setFakeWxt } from '../../utils/testing/fake-objects'; import { mock } from 'vitest-mock-extended'; import { createSafariRunner } from '../safari'; import { createWslRunner } from '../wsl'; import { createManualRunner } from '../manual'; -import { isWsl } from '../../utils/wsl'; +import { hasGuiDisplay, isWsl } from '../../utils/wsl'; import { createWebExtRunner } from '../web-ext'; import { createWxtRunner } from '../wxt-runner'; import { ExtensionRunner } from '../../../types'; vi.mock('../../utils/wsl'); const isWslMock = vi.mocked(isWsl); +const hasGuiDisplayMock = vi.mocked(hasGuiDisplay); vi.mock('../safari'); const createSafariRunnerMock = vi.mocked(createSafariRunner); @@ -30,9 +31,15 @@ const createWxtRunnerMock = vi.mocked(createWxtRunner); describe('createExtensionRunner', () => { const originalDisplay = process.env.DISPLAY; + const originalWaylandDisplay = process.env.WAYLAND_DISPLAY; + + beforeEach(() => { + vi.clearAllMocks(); + }); afterEach(() => { process.env.DISPLAY = originalDisplay; + process.env.WAYLAND_DISPLAY = originalWaylandDisplay; }); it('should return a Safari runner when browser is "safari"', async () => { @@ -47,9 +54,9 @@ describe('createExtensionRunner', () => { await expect(createExtensionRunner()).resolves.toBe(safariRunner); }); - it('should return a WSL runner when `is-wsl` is true and DISPLAY is not :0', async () => { - process.env.DISPLAY = ':1'; + it('should return a WSL runner when `is-wsl` is true and no GUI is available', async () => { isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(false); setFakeWxt({ config: { browser: 'chrome', @@ -61,9 +68,23 @@ describe('createExtensionRunner', () => { await expect(createExtensionRunner()).resolves.toBe(wslRunner); }); - it('should return an internal runner when `is-wsl` is true and DISPLAY is :0 for Chrome/Chromium', async () => { - process.env.DISPLAY = ':0'; + it('should return an internal runner when `is-wsl` is true and DISPLAY is set for Chrome/Chromium', async () => { + isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(true); + setFakeWxt({ + config: { + browser: 'chrome', + }, + }); + const internalRunner = mock(); + createWxtRunnerMock.mockReturnValue(internalRunner); + + await expect(createExtensionRunner()).resolves.toBe(internalRunner); + }); + + it('should return an internal runner when `is-wsl` is true and WAYLAND_DISPLAY is set for Chrome/Chromium', async () => { isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(true); setFakeWxt({ config: { browser: 'chrome', @@ -75,9 +96,9 @@ describe('createExtensionRunner', () => { await expect(createExtensionRunner()).resolves.toBe(internalRunner); }); - it('should return an internal runner when `is-wsl` is true and DISPLAY is :0 for Firefox', async () => { - process.env.DISPLAY = ':0'; + it('should return an internal runner when `is-wsl` is true and GUI is available for Firefox', async () => { isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(true); setFakeWxt({ config: { browser: 'firefox', diff --git a/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts b/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts index f85d8ca61..0aaf2b890 100644 --- a/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts +++ b/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts @@ -1,7 +1,7 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createWebExtRunner } from '../web-ext'; import { setFakeWxt } from '../../utils/testing/fake-objects'; -import { isWsl } from '../../utils/wsl'; +import { hasGuiDisplay, isWsl } from '../../utils/wsl'; vi.mock('node:fs/promises', () => ({ access: vi.fn(async (filePath: string) => { @@ -13,13 +13,14 @@ vi.mock('node:fs/promises', () => ({ vi.mock('../../utils/wsl'); const isWslMock = vi.mocked(isWsl); +const hasGuiDisplayMock = vi.mocked(hasGuiDisplay); -const cmdRun = vi.fn(); +const cmdRunMock = vi.fn(); vi.mock('web-ext-run', () => ({ default: { cmd: { - run: cmdRun, + run: cmdRunMock, }, }, })); @@ -30,19 +31,24 @@ vi.mock('web-ext-run/util/logger', () => ({ }, })); -describe('createWebExtRunner (WSLg)', () => { +describe('createWebExtRunner (WSL with GUI)', () => { const originalDisplay = process.env.DISPLAY; + const originalWaylandDisplay = process.env.WAYLAND_DISPLAY; + + beforeEach(() => { + vi.clearAllMocks(); + }); afterEach(() => { process.env.DISPLAY = originalDisplay; - cmdRun.mockReset(); + process.env.WAYLAND_DISPLAY = originalWaylandDisplay; }); - it('should ignore Windows-style binaries on WSLg so CDP pipes can work', async () => { - process.env.DISPLAY = ':0'; + it('should ignore Windows-style binaries in WSL with GUI so CDP pipes can work', async () => { isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(true); - cmdRun.mockResolvedValueOnce({ + cmdRunMock.mockResolvedValueOnce({ exit: vi.fn(), }); @@ -65,16 +71,17 @@ describe('createWebExtRunner (WSLg)', () => { const runner = createWebExtRunner(); await runner.openBrowser(); - expect(cmdRun).toHaveBeenCalledTimes(1); - const finalConfig = cmdRun.mock.calls[0][0] as Record; + expect(cmdRunMock).toHaveBeenCalledTimes(1); + expect(cmdRunMock.mock.calls[0]).toBeDefined(); + const finalConfig = cmdRunMock.mock.calls[0][0] as Record; expect(finalConfig.chromiumBinary).toBe('/opt/google/chrome/chrome'); }); - it('should coerce chromiumArgs --user-data-dir into chromiumProfile + keepProfileChanges on WSLg', async () => { - process.env.DISPLAY = ':0'; + it('should coerce chromiumArgs --user-data-dir into chromiumProfile + keepProfileChanges in WSL with GUI', async () => { isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(true); - cmdRun.mockResolvedValueOnce({ + cmdRunMock.mockResolvedValueOnce({ exit: vi.fn(), }); @@ -98,8 +105,9 @@ describe('createWebExtRunner (WSLg)', () => { const runner = createWebExtRunner(); await runner.openBrowser(); - expect(cmdRun).toHaveBeenCalledTimes(1); - const finalConfig = cmdRun.mock.calls[0][0] as Record; + expect(cmdRunMock).toHaveBeenCalledTimes(1); + expect(cmdRunMock.mock.calls[0]).toBeDefined(); + const finalConfig = cmdRunMock.mock.calls[0][0] as Record; expect(finalConfig.chromiumProfile).toBe( '/home/user/project/.wxt/chrome-data', ); @@ -114,4 +122,110 @@ describe('createWebExtRunner (WSLg)', () => { expect.arrayContaining([expect.stringMatching(/--user-data-dir/)]), ); }); + + it('should not apply WSL with GUI workarounds when not running in WSL', async () => { + isWslMock.mockResolvedValueOnce(false); + hasGuiDisplayMock.mockReturnValueOnce(true); + + cmdRunMock.mockResolvedValueOnce({ + exit: vi.fn(), + }); + + setFakeWxt({ + config: { + browser: 'chrome', + manifestVersion: 3, + outDir: '/tmp/wxt-out', + runnerConfig: { + config: { + binaries: { + chrome: + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + }, + }, + }, + }, + }); + + const runner = createWebExtRunner(); + await runner.openBrowser(); + + expect(cmdRunMock).toHaveBeenCalledTimes(1); + expect(cmdRunMock.mock.calls[0]).toBeDefined(); + const finalConfig = cmdRunMock.mock.calls[0][0] as Record; + // Windows binary should be preserved when not in WSL + expect(finalConfig.chromiumBinary).toBe( + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + ); + }); + + it('should not apply WSL with GUI workarounds when no GUI is available', async () => { + isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(false); + + cmdRunMock.mockResolvedValueOnce({ + exit: vi.fn(), + }); + + setFakeWxt({ + config: { + browser: 'chrome', + manifestVersion: 3, + outDir: '/tmp/wxt-out', + runnerConfig: { + config: { + binaries: { + chrome: + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + }, + }, + }, + }, + }); + + const runner = createWebExtRunner(); + await runner.openBrowser(); + + expect(cmdRunMock).toHaveBeenCalledTimes(1); + expect(cmdRunMock.mock.calls[0]).toBeDefined(); + const finalConfig = cmdRunMock.mock.calls[0][0] as Record; + // Windows binary should be preserved when no GUI is available + expect(finalConfig.chromiumBinary).toBe( + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + ); + }); + + it('should apply workarounds when WAYLAND_DISPLAY is set in WSL', async () => { + isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(true); + + cmdRunMock.mockResolvedValueOnce({ + exit: vi.fn(), + }); + + setFakeWxt({ + config: { + browser: 'chrome', + manifestVersion: 3, + outDir: '/tmp/wxt-out', + runnerConfig: { + config: { + binaries: { + chrome: + 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', + }, + }, + }, + }, + }); + + const runner = createWebExtRunner(); + await runner.openBrowser(); + + expect(cmdRunMock).toHaveBeenCalledTimes(1); + expect(cmdRunMock.mock.calls[0]).toBeDefined(); + const finalConfig = cmdRunMock.mock.calls[0][0] as Record; + // Windows binary should be ignored, Linux binary used instead + expect(finalConfig.chromiumBinary).toBe('/opt/google/chrome/chrome'); + }); }); diff --git a/packages/wxt/src/core/runners/__tests__/wsl.test.ts b/packages/wxt/src/core/runners/__tests__/wsl.test.ts index 22c4c5bf6..f56fb536d 100644 --- a/packages/wxt/src/core/runners/__tests__/wsl.test.ts +++ b/packages/wxt/src/core/runners/__tests__/wsl.test.ts @@ -1,16 +1,9 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { createWslRunner } from '../wsl'; import { setFakeWxt } from '../../utils/testing/fake-objects'; describe('createWslRunner', () => { - const originalDisplay = process.env.DISPLAY; - - afterEach(() => { - process.env.DISPLAY = originalDisplay; - }); - - it('should warn when running in WSL without WSLg', async () => { - process.env.DISPLAY = ':0'; + it('should warn when running in WSL without a GUI environment', async () => { const fake = setFakeWxt({ config: { browser: 'chrome', @@ -22,5 +15,10 @@ describe('createWslRunner', () => { await runner.openBrowser(); expect(fake.logger.warn).toHaveBeenCalledTimes(1); + expect(fake.logger.warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Cannot auto-open browser when using WSL without a GUI environment', + ), + ); }); }); diff --git a/packages/wxt/src/core/runners/index.ts b/packages/wxt/src/core/runners/index.ts index 7b56028a7..0e621b5a0 100644 --- a/packages/wxt/src/core/runners/index.ts +++ b/packages/wxt/src/core/runners/index.ts @@ -4,7 +4,7 @@ import { createWebExtRunner } from './web-ext'; import { createSafariRunner } from './safari'; import { createManualRunner } from './manual'; import { createWxtRunner } from './wxt-runner'; -import { isWsl } from '../utils/wsl'; +import { hasGuiDisplay, isWsl } from '../utils/wsl'; import { wxt } from '../wxt'; import { KNOWN_BROWSER_PATHS, type KnownTarget } from '@wxt-dev/runner'; @@ -19,13 +19,14 @@ export async function createExtensionRunner(): Promise { if (wxt.config.runnerConfig.config?.disabled) return createManualRunner(); const runningInWsl = await isWsl(); - const isWslg = process.env.DISPLAY === ':0'; - if (runningInWsl && !isWslg) return createWslRunner(); + const hasGui = hasGuiDisplay(); + if (runningInWsl && !hasGui) return createWslRunner(); - // On WSLg, prefer WXT's own runner for browsers supported by @wxt-dev/runner. - // This avoids web-ext-run / chrome-launcher WSL path rewriting (e.g. \\wsl.localhost\...) - // and keeps temp/profile directories on the Linux filesystem. - if (runningInWsl && isWslg && isKnownTarget(wxt.config.browser)) { + // When running in WSL with a GUI (WSLg, VcXsrv, X410, etc.), prefer WXT's own runner + // for browsers supported by @wxt-dev/runner. This avoids web-ext-run / chrome-launcher + // WSL path rewriting (e.g. \\wsl.localhost\...) and keeps temp/profile directories + // on the Linux filesystem. + if (runningInWsl && hasGui && isKnownTarget(wxt.config.browser)) { return createWxtRunner(); } diff --git a/packages/wxt/src/core/runners/web-ext.ts b/packages/wxt/src/core/runners/web-ext.ts index 8809deb8b..83f1b1971 100644 --- a/packages/wxt/src/core/runners/web-ext.ts +++ b/packages/wxt/src/core/runners/web-ext.ts @@ -3,7 +3,7 @@ import { ExtensionRunner } from '../../types'; import { formatDuration } from '../utils/time'; import defu from 'defu'; import { wxt } from '../wxt'; -import { isWsl } from '../utils/wsl'; +import { hasGuiDisplay, isWsl } from '../utils/wsl'; import * as fs from 'node:fs/promises'; import { constants as fsConstants } from 'node:fs'; import path from 'node:path'; @@ -41,15 +41,15 @@ export function createWebExtRunner(): ExtensionRunner { const wxtUserConfig = wxt.config.runnerConfig.config; const runningInWsl = await isWsl(); - const runningInWslg = runningInWsl && process.env.DISPLAY === ':0'; + const runningInWslWithGui = runningInWsl && hasGuiDisplay(); const sanitizePathForWslg = ( value: string | undefined, label: string, ) => { - if (!runningInWslg || value == null) return value; + if (!runningInWslWithGui || value == null) return value; if (isWindowsPath(value)) { wxt.logger.warn( - `[web-ext] Ignoring ${label}="${value}" on WSLg (DISPLAY=:0). Windows paths/binaries are incompatible with the CDP pipe used to load extensions. Install a Linux browser in WSL and omit this setting.`, + `[web-ext] Ignoring ${label}="${value}" in WSL with GUI. Windows paths/binaries are incompatible with the CDP pipe used to load extensions. Install a Linux browser in WSL and omit this setting.`, ); return undefined; } @@ -65,7 +65,7 @@ export function createWebExtRunner(): ExtensionRunner { ); const chromiumBinary = await resolveChromiumBinaryForRemoteDebuggingPipe({ chromiumBinary: chromiumBinaryFromConfig, - runningInWslg, + runningInWslWithGui, }); const chromiumUserDataDirOverride = @@ -73,7 +73,7 @@ export function createWebExtRunner(): ExtensionRunner { ? undefined : extractUserDataDirFromChromiumArgs(wxtUserConfig?.chromiumArgs); const shouldCoerceUserDataDirToProfile = - runningInWslg && + runningInWslWithGui && wxt.config.browser !== 'firefox' && wxtUserConfig?.chromiumProfile == null && wxtUserConfig?.keepProfileChanges == null && @@ -98,7 +98,7 @@ export function createWebExtRunner(): ExtensionRunner { : // Match the Windows docs behavior when a profile directory is used. // This prevents web-ext-run from creating a brand new temp profile on every launch. (wxtUserConfig?.keepProfileChanges ?? - (runningInWslg && coercedChromiumProfile != null + (runningInWslWithGui && coercedChromiumProfile != null ? true : undefined)); @@ -111,16 +111,16 @@ export function createWebExtRunner(): ExtensionRunner { if (shouldCoerceUserDataDirToProfile) { wxt.logger.warn( - `[web-ext] On WSLg (DISPLAY=:0), converting chromiumArgs "--user-data-dir" into { chromiumProfile, keepProfileChanges: true } (Windows-style) to avoid creating throwaway profiles on each launch.`, + `[web-ext] In WSL with GUI, converting chromiumArgs "--user-data-dir" into { chromiumProfile, keepProfileChanges: true } to avoid creating throwaway profiles on each launch.`, ); } else if ( - runningInWslg && + runningInWslWithGui && wxt.config.browser !== 'firefox' && wxtUserConfig?.chromiumProfile != null && wxtUserConfig?.keepProfileChanges == null ) { wxt.logger.warn( - `[web-ext] On WSLg (DISPLAY=:0), defaulting keepProfileChanges=true because chromiumProfile is set (Windows-style) to avoid creating throwaway profiles on each launch.`, + `[web-ext] In WSL with GUI, defaulting keepProfileChanges=true because chromiumProfile is set to avoid creating throwaway profiles on each launch.`, ); } @@ -191,14 +191,14 @@ export function createWebExtRunner(): ExtensionRunner { async function resolveChromiumBinaryForRemoteDebuggingPipe({ chromiumBinary, - runningInWslg, + runningInWslWithGui, }: { chromiumBinary: string | undefined; - runningInWslg: boolean; + runningInWslWithGui: boolean; }): Promise { - if (!runningInWslg) return chromiumBinary; + if (!runningInWslWithGui) return chromiumBinary; - // On WSLg, Chrome's wrapper script (google-chrome / google-chrome-stable) + // In WSL with GUI, Chrome's wrapper script (google-chrome / google-chrome-stable) // uses bash process substitution which closes extra FDs on exec. // That breaks Chrome's `--remote-debugging-pipe` mode and causes CDP to // disconnect immediately. @@ -214,7 +214,7 @@ async function resolveChromiumBinaryForRemoteDebuggingPipe({ if (hasRealGoogleChrome && looksLikeGoogleChromeWrapper(chromiumBinary)) { wxt.logger.warn( - `[web-ext] Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" on WSLg to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, + `[web-ext] Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" in WSL with GUI to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, ); return googleChromeRealBinary; } diff --git a/packages/wxt/src/core/runners/wsl.ts b/packages/wxt/src/core/runners/wsl.ts index 3da1422a0..32f5fe351 100644 --- a/packages/wxt/src/core/runners/wsl.ts +++ b/packages/wxt/src/core/runners/wsl.ts @@ -5,20 +5,20 @@ import { wxt } from '../wxt'; /** * WSL sometimes cannot launch browsers. * - * Note: WSLg (DISPLAY=:0) is handled in `createExtensionRunner`. + * Note: WSL with GUI (DISPLAY or WAYLAND_DISPLAY set) is handled in `createExtensionRunner`. */ export function createWslRunner(): ExtensionRunner { return { async openBrowser() { wxt.logger.warn( - `Cannot auto-open browser when using WSL without WSLg (DISPLAY=:0). Load "${relative( + `Cannot auto-open browser when using WSL without a GUI environment (no DISPLAY or WAYLAND_DISPLAY set). Load "${relative( process.cwd(), wxt.config.outDir, )}" as an unpacked extension manually`, ); }, async closeBrowser() { - // No-op + // noop }, }; } diff --git a/packages/wxt/src/core/runners/wxt-runner.ts b/packages/wxt/src/core/runners/wxt-runner.ts index 2e6299a9a..f5dfeceb4 100644 --- a/packages/wxt/src/core/runners/wxt-runner.ts +++ b/packages/wxt/src/core/runners/wxt-runner.ts @@ -1,6 +1,7 @@ import { ExtensionRunner } from '../../types'; import { formatDuration } from '../utils/time'; import { wxt } from '../wxt'; +import { hasGuiDisplay } from '../utils/wsl'; import { KNOWN_BROWSER_PATHS, run, @@ -56,18 +57,18 @@ export function createWxtRunner(): ExtensionRunner { ); } - const runningInWslg = process.env.DISPLAY === ':0'; + const runningInWslWithGui = hasGuiDisplay(); - const binaryFromConfig = sanitizePathForWslg( + const binaryFromConfig = sanitizePathForWslWithGui( userConfig?.binaries?.[browser], `binaries.${browser}`, - runningInWslg, + runningInWslWithGui, ); const browserBinaryOverride = !isFirefoxFamilyTarget(browser) ? await resolveChromiumBinaryForRemoteDebuggingPipe({ chromiumBinary: binaryFromConfig, - runningInWslg, + runningInWslWithGui, }) : binaryFromConfig; @@ -79,10 +80,10 @@ export function createWxtRunner(): ExtensionRunner { const firefoxArgs: string[] = [...(userConfig?.firefoxArgs ?? [])]; if (startUrls) firefoxArgs.push(...startUrls); - const firefoxProfile = sanitizePathForWslg( + const firefoxProfile = sanitizePathForWslWithGui( userConfig?.firefoxProfile, 'firefoxProfile', - runningInWslg, + runningInWslWithGui, ); const dataPersistence = @@ -123,10 +124,10 @@ export function createWxtRunner(): ExtensionRunner { chromiumArgs.push(...startUrls); } - const chromiumProfile = sanitizePathForWslg( + const chromiumProfile = sanitizePathForWslWithGui( userConfig?.chromiumProfile, 'chromiumProfile', - runningInWslg, + runningInWslWithGui, ); const dataPersistence = @@ -167,15 +168,15 @@ export function createWxtRunner(): ExtensionRunner { }; } -function sanitizePathForWslg( +function sanitizePathForWslWithGui( value: string | undefined, label: string, - runningInWslg: boolean, + runningInWslWithGui: boolean, ): string | undefined { - if (!runningInWslg || value == null) return value; + if (!runningInWslWithGui || value == null) return value; if (isWindowsPath(value)) { wxt.logger.warn( - `[runner] Ignoring ${label}="${value}" on WSLg (DISPLAY=:0). Windows paths/binaries are incompatible with CDP pipe extension install. Install a Linux browser in WSL and omit this setting.`, + `[runner] Ignoring ${label}="${value}" in WSL with GUI. Windows paths/binaries are incompatible with CDP pipe extension install. Install a Linux browser in WSL and omit this setting.`, ); return undefined; } @@ -194,12 +195,12 @@ function isWindowsPath(value: string): boolean { async function resolveChromiumBinaryForRemoteDebuggingPipe({ chromiumBinary, - runningInWslg, + runningInWslWithGui, }: { chromiumBinary: string | undefined; - runningInWslg: boolean; + runningInWslWithGui: boolean; }): Promise { - if (!runningInWslg) return chromiumBinary; + if (!runningInWslWithGui) return chromiumBinary; const googleChromeRealBinary = '/opt/google/chrome/chrome'; const hasRealGoogleChrome = await isExecutable(googleChromeRealBinary); @@ -211,7 +212,7 @@ async function resolveChromiumBinaryForRemoteDebuggingPipe({ if (hasRealGoogleChrome && looksLikeGoogleChromeWrapper(chromiumBinary)) { wxt.logger.warn( - `[runner] Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" on WSLg to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, + `[runner] Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" in WSL with GUI to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, ); return googleChromeRealBinary; } diff --git a/packages/wxt/src/core/utils/wsl.ts b/packages/wxt/src/core/utils/wsl.ts index 8b2ce400e..1cce347ff 100644 --- a/packages/wxt/src/core/utils/wsl.ts +++ b/packages/wxt/src/core/utils/wsl.ts @@ -5,3 +5,16 @@ export async function isWsl(): Promise { const { default: isWsl } = await import('is-wsl'); // ESM only, requires dynamic import return isWsl; } + +/** + * Returns true when a GUI display environment is available. + * Checks for both X11 (DISPLAY) and Wayland (WAYLAND_DISPLAY) environments. + */ +export function hasGuiDisplay(): boolean { + const display = process.env.DISPLAY; + const waylandDisplay = process.env.WAYLAND_DISPLAY; + return ( + (typeof display === 'string' && display.length > 0) || + (typeof waylandDisplay === 'string' && waylandDisplay.length > 0) + ); +} From 0af448549909d12fc72ca19b00129882f20ce6ff Mon Sep 17 00:00:00 2001 From: Seismix Date: Mon, 26 Jan 2026 16:26:37 +0100 Subject: [PATCH 3/7] refactor: extract shared browser utilities to reduce duplication --- .../wxt/src/core/runners/browser-utils.ts | 118 ++++++++++++++++++ packages/wxt/src/core/runners/web-ext.ts | 117 +++-------------- packages/wxt/src/core/runners/wxt-runner.ts | 98 ++------------- 3 files changed, 142 insertions(+), 191 deletions(-) create mode 100644 packages/wxt/src/core/runners/browser-utils.ts diff --git a/packages/wxt/src/core/runners/browser-utils.ts b/packages/wxt/src/core/runners/browser-utils.ts new file mode 100644 index 000000000..cfcbb2f18 --- /dev/null +++ b/packages/wxt/src/core/runners/browser-utils.ts @@ -0,0 +1,118 @@ +import * as fs from 'node:fs/promises'; +import { constants as fsConstants } from 'node:fs'; +import path from 'node:path'; +import { wxt } from '../wxt'; + +/** + * Check if a path looks like a Windows path (drive letter, UNC, or WSL mount). + */ +export function isWindowsPath(value: string): boolean { + // Windows drive path: C:\... + if (/^[a-zA-Z]:\\/.test(value)) return true; + // WSL mounted drive: /mnt/c/... + if (/^\/mnt\/[a-zA-Z]\//.test(value)) return true; + // UNC-ish + if (value.startsWith('\\\\')) return true; + return false; +} + +/** + * Check if a file is executable. + */ +export async function isExecutable(filePath: string): Promise { + try { + await fs.access(filePath, fsConstants.X_OK); + return true; + } catch { + return false; + } +} + +/** + * Check if a binary path looks like a Google Chrome wrapper script. + */ +export function looksLikeGoogleChromeWrapper(filePath: string): boolean { + const base = path.basename(filePath); + if (base === 'google-chrome') return true; + if (base === 'google-chrome-stable') return true; + if (base === 'google-chrome-beta') return true; + if (base === 'google-chrome-dev') return true; + if (base === 'google-chrome-unstable') return true; + if (filePath === '/opt/google/chrome/google-chrome') return true; + return false; +} + +/** + * Resolve a profile directory path, making relative paths absolute. + */ +export function resolveProfilePath( + projectRoot: string, + profileDir: string, +): string { + return path.isAbsolute(profileDir) + ? profileDir + : path.resolve(projectRoot, profileDir); +} + +/** + * In WSL with GUI, Chrome's wrapper scripts use bash process substitution which + * closes extra FDs on exec, breaking `--remote-debugging-pipe` mode. + * This resolves to the actual Chrome binary to keep the CDP pipe open. + */ +export async function resolveChromiumBinaryForRemoteDebuggingPipe({ + chromiumBinary, + runningInWslWithGui, + loggerPrefix = '', +}: { + chromiumBinary: string | undefined; + runningInWslWithGui: boolean; + loggerPrefix?: string; +}): Promise { + if (!runningInWslWithGui) return chromiumBinary; + + const googleChromeRealBinary = '/opt/google/chrome/chrome'; + const hasRealGoogleChrome = await isExecutable(googleChromeRealBinary); + + if (chromiumBinary == null) { + if (hasRealGoogleChrome) return googleChromeRealBinary; + return chromiumBinary; + } + + if (hasRealGoogleChrome && looksLikeGoogleChromeWrapper(chromiumBinary)) { + wxt.logger.warn( + `${loggerPrefix}Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" in WSL with GUI to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, + ); + return googleChromeRealBinary; + } + + // Handle cases where a wrapper was explicitly provided from a non-/opt path. + if (looksLikeGoogleChromeWrapper(chromiumBinary)) { + const resolved = await fs + .realpath(chromiumBinary) + .catch(() => chromiumBinary); + const sibling = path.join(path.dirname(resolved), 'chrome'); + if (await isExecutable(sibling)) return sibling; + } + + return chromiumBinary; +} + +/** + * Sanitize paths for WSL with GUI, filtering out Windows paths that won't work + * with CDP pipes. + */ +export function sanitizePathForWslWithGui( + value: string | undefined, + label: string, + runningInWslWithGui: boolean, + loggerPrefix: string, +): string | undefined { + if (!runningInWslWithGui || value == null) return value; + if (isWindowsPath(value)) { + wxt.logger.warn( + `${loggerPrefix}Ignoring ${label}="${value}" in WSL with GUI. Windows paths/binaries are incompatible with CDP pipe extension install. Install a Linux browser in WSL and omit this setting.`, + ); + return undefined; + } + return value; +} diff --git a/packages/wxt/src/core/runners/web-ext.ts b/packages/wxt/src/core/runners/web-ext.ts index 83f1b1971..44240493b 100644 --- a/packages/wxt/src/core/runners/web-ext.ts +++ b/packages/wxt/src/core/runners/web-ext.ts @@ -4,8 +4,12 @@ import { formatDuration } from '../utils/time'; import defu from 'defu'; import { wxt } from '../wxt'; import { hasGuiDisplay, isWsl } from '../utils/wsl'; -import * as fs from 'node:fs/promises'; -import { constants as fsConstants } from 'node:fs'; +import { + isWindowsPath, + resolveChromiumBinaryForRemoteDebuggingPipe, + resolveProfilePath, + sanitizePathForWslWithGui, +} from './browser-utils'; import path from 'node:path'; /** @@ -42,19 +46,13 @@ export function createWebExtRunner(): ExtensionRunner { const runningInWsl = await isWsl(); const runningInWslWithGui = runningInWsl && hasGuiDisplay(); - const sanitizePathForWslg = ( - value: string | undefined, - label: string, - ) => { - if (!runningInWslWithGui || value == null) return value; - if (isWindowsPath(value)) { - wxt.logger.warn( - `[web-ext] Ignoring ${label}="${value}" in WSL with GUI. Windows paths/binaries are incompatible with the CDP pipe used to load extensions. Install a Linux browser in WSL and omit this setting.`, - ); - return undefined; - } - return value; - }; + const sanitizePathForWslg = (value: string | undefined, label: string) => + sanitizePathForWslWithGui( + value, + label, + runningInWslWithGui, + '[web-ext] ', + ); const chromiumBinaryFromConfig = wxt.config.browser === 'firefox' @@ -66,6 +64,7 @@ export function createWebExtRunner(): ExtensionRunner { const chromiumBinary = await resolveChromiumBinaryForRemoteDebuggingPipe({ chromiumBinary: chromiumBinaryFromConfig, runningInWslWithGui, + loggerPrefix: '[web-ext] ', }); const chromiumUserDataDirOverride = @@ -81,10 +80,7 @@ export function createWebExtRunner(): ExtensionRunner { const coercedChromiumProfile = shouldCoerceUserDataDirToProfile ? sanitizePathForWslg( - resolveChromiumProfilePath( - wxt.config.root, - chromiumUserDataDirOverride, - ), + resolveProfilePath(wxt.config.root, chromiumUserDataDirOverride), 'chromiumProfile', ) : sanitizePathForWslg( @@ -189,89 +185,6 @@ export function createWebExtRunner(): ExtensionRunner { }; } -async function resolveChromiumBinaryForRemoteDebuggingPipe({ - chromiumBinary, - runningInWslWithGui, -}: { - chromiumBinary: string | undefined; - runningInWslWithGui: boolean; -}): Promise { - if (!runningInWslWithGui) return chromiumBinary; - - // In WSL with GUI, Chrome's wrapper script (google-chrome / google-chrome-stable) - // uses bash process substitution which closes extra FDs on exec. - // That breaks Chrome's `--remote-debugging-pipe` mode and causes CDP to - // disconnect immediately. - // - // Prefer the actual Chrome binary to keep the CDP pipe open. - const googleChromeRealBinary = '/opt/google/chrome/chrome'; - const hasRealGoogleChrome = await isExecutable(googleChromeRealBinary); - - if (chromiumBinary == null) { - if (hasRealGoogleChrome) return googleChromeRealBinary; - return chromiumBinary; - } - - if (hasRealGoogleChrome && looksLikeGoogleChromeWrapper(chromiumBinary)) { - wxt.logger.warn( - `[web-ext] Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" in WSL with GUI to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, - ); - return googleChromeRealBinary; - } - - // Handle cases where a wrapper was explicitly provided from a non-/opt path. - if (looksLikeGoogleChromeWrapper(chromiumBinary)) { - const resolved = await fs - .realpath(chromiumBinary) - .catch(() => chromiumBinary); - const sibling = path.join(path.dirname(resolved), 'chrome'); - if (await isExecutable(sibling)) return sibling; - } - - return chromiumBinary; -} - -async function isExecutable(filePath: string): Promise { - try { - await fs.access(filePath, fsConstants.X_OK); - return true; - } catch { - return false; - } -} - -function looksLikeGoogleChromeWrapper(filePath: string): boolean { - const base = path.basename(filePath); - if (base === 'google-chrome') return true; - if (base === 'google-chrome-stable') return true; - if (base === 'google-chrome-beta') return true; - if (base === 'google-chrome-dev') return true; - if (base === 'google-chrome-unstable') return true; - if (filePath === '/opt/google/chrome/google-chrome') return true; - return false; -} - -function isWindowsPath(value: string): boolean { - // Windows drive path: C:\... - if (/^[a-zA-Z]:\\/.test(value)) return true; - // WSL mounted drive: /mnt/c/... - if (/^\/mnt\/[a-zA-Z]\//.test(value)) return true; - // UNC-ish - if (value.startsWith('\\\\')) return true; - return false; -} - -function resolveChromiumProfilePath( - projectRoot: string, - userDataDir: string, -): string { - // If the user gave a relative path (common in Linux docs), make it absolute. - // This matches the Windows docs requirement and avoids depending on CWD. - return path.isAbsolute(userDataDir) - ? userDataDir - : path.resolve(projectRoot, userDataDir); -} - function extractUserDataDirFromChromiumArgs( chromiumArgs: string[] | undefined, ): string | undefined { diff --git a/packages/wxt/src/core/runners/wxt-runner.ts b/packages/wxt/src/core/runners/wxt-runner.ts index f5dfeceb4..3137aef30 100644 --- a/packages/wxt/src/core/runners/wxt-runner.ts +++ b/packages/wxt/src/core/runners/wxt-runner.ts @@ -8,9 +8,11 @@ import { type KnownTarget, type Runner as WxtRunnerInstance, } from '@wxt-dev/runner'; -import * as fs from 'node:fs/promises'; -import { constants as fsConstants } from 'node:fs'; -import path from 'node:path'; +import { + resolveChromiumBinaryForRemoteDebuggingPipe, + resolveProfilePath, + sanitizePathForWslWithGui, +} from './browser-utils'; const KNOWN_TARGETS = new Set(Object.keys(KNOWN_BROWSER_PATHS)); function isKnownTarget(value: string): value is KnownTarget { @@ -63,12 +65,14 @@ export function createWxtRunner(): ExtensionRunner { userConfig?.binaries?.[browser], `binaries.${browser}`, runningInWslWithGui, + '[runner] ', ); const browserBinaryOverride = !isFirefoxFamilyTarget(browser) ? await resolveChromiumBinaryForRemoteDebuggingPipe({ chromiumBinary: binaryFromConfig, runningInWslWithGui, + loggerPrefix: '[runner] ', }) : binaryFromConfig; @@ -84,6 +88,7 @@ export function createWxtRunner(): ExtensionRunner { userConfig?.firefoxProfile, 'firefoxProfile', runningInWslWithGui, + '[runner] ', ); const dataPersistence = @@ -128,6 +133,7 @@ export function createWxtRunner(): ExtensionRunner { userConfig?.chromiumProfile, 'chromiumProfile', runningInWslWithGui, + '[runner] ', ); const dataPersistence = @@ -167,89 +173,3 @@ export function createWxtRunner(): ExtensionRunner { }, }; } - -function sanitizePathForWslWithGui( - value: string | undefined, - label: string, - runningInWslWithGui: boolean, -): string | undefined { - if (!runningInWslWithGui || value == null) return value; - if (isWindowsPath(value)) { - wxt.logger.warn( - `[runner] Ignoring ${label}="${value}" in WSL with GUI. Windows paths/binaries are incompatible with CDP pipe extension install. Install a Linux browser in WSL and omit this setting.`, - ); - return undefined; - } - return value; -} - -function isWindowsPath(value: string): boolean { - // Windows drive path: C:\... - if (/^[a-zA-Z]:\\/.test(value)) return true; - // WSL mounted drive: /mnt/c/... - if (/^\/mnt\/[a-zA-Z]\//.test(value)) return true; - // UNC-ish - if (value.startsWith('\\\\')) return true; - return false; -} - -async function resolveChromiumBinaryForRemoteDebuggingPipe({ - chromiumBinary, - runningInWslWithGui, -}: { - chromiumBinary: string | undefined; - runningInWslWithGui: boolean; -}): Promise { - if (!runningInWslWithGui) return chromiumBinary; - - const googleChromeRealBinary = '/opt/google/chrome/chrome'; - const hasRealGoogleChrome = await isExecutable(googleChromeRealBinary); - - if (chromiumBinary == null) { - if (hasRealGoogleChrome) return googleChromeRealBinary; - return chromiumBinary; - } - - if (hasRealGoogleChrome && looksLikeGoogleChromeWrapper(chromiumBinary)) { - wxt.logger.warn( - `[runner] Using "${googleChromeRealBinary}" instead of "${chromiumBinary}" in WSL with GUI to keep the CDP pipe open (avoids "Remote debugging pipe file descriptors are not open").`, - ); - return googleChromeRealBinary; - } - - if (looksLikeGoogleChromeWrapper(chromiumBinary)) { - const resolved = await fs - .realpath(chromiumBinary) - .catch(() => chromiumBinary); - const sibling = path.join(path.dirname(resolved), 'chrome'); - if (await isExecutable(sibling)) return sibling; - } - - return chromiumBinary; -} - -async function isExecutable(filePath: string): Promise { - try { - await fs.access(filePath, fsConstants.X_OK); - return true; - } catch { - return false; - } -} - -function looksLikeGoogleChromeWrapper(filePath: string): boolean { - const base = path.basename(filePath); - if (base === 'google-chrome') return true; - if (base === 'google-chrome-stable') return true; - if (base === 'google-chrome-beta') return true; - if (base === 'google-chrome-dev') return true; - if (base === 'google-chrome-unstable') return true; - if (filePath === '/opt/google/chrome/google-chrome') return true; - return false; -} - -function resolveProfilePath(projectRoot: string, profileDir: string): string { - return path.isAbsolute(profileDir) - ? profileDir - : path.resolve(projectRoot, profileDir); -} From 5413e781499b6817bc134a97270ba5d64ec59654 Mon Sep 17 00:00:00 2001 From: Seismix Date: Tue, 27 Jan 2026 09:34:17 +0100 Subject: [PATCH 4/7] feat: add browser installation checks for WSL with GUI --- .../runners/__tests__/web-ext.wsl.test.ts | 54 +++++++++++++++++++ .../wxt/src/core/runners/browser-utils.ts | 17 ++++++ packages/wxt/src/core/runners/web-ext.ts | 29 ++++++++++ packages/wxt/src/core/runners/wxt-runner.ts | 11 ++++ 4 files changed, 111 insertions(+) diff --git a/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts b/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts index 0aaf2b890..3e35d628f 100644 --- a/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts +++ b/packages/wxt/src/core/runners/__tests__/web-ext.wsl.test.ts @@ -228,4 +228,58 @@ describe('createWebExtRunner (WSL with GUI)', () => { // Windows binary should be ignored, Linux binary used instead expect(finalConfig.chromiumBinary).toBe('/opt/google/chrome/chrome'); }); + + it('should throw error when Chrome is not installed in WSL with GUI', async () => { + isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(true); + + // Mock fs.access to reject for all browser paths + const fsMock = await import('node:fs/promises'); + vi.mocked(fsMock.access).mockImplementation(async () => { + throw new Error('ENOENT'); + }); + + setFakeWxt({ + config: { + browser: 'chrome', + manifestVersion: 3, + outDir: '/tmp/wxt-out', + runnerConfig: { + config: {}, + }, + }, + }); + + const runner = createWebExtRunner(); + await expect(runner.openBrowser()).rejects.toThrow( + 'Browser "chrome" not found in WSL', + ); + }); + + it('should throw error when Firefox is not installed in WSL with GUI', async () => { + isWslMock.mockResolvedValueOnce(true); + hasGuiDisplayMock.mockReturnValueOnce(true); + + // Mock fs.access to reject for all browser paths + const fsMock = await import('node:fs/promises'); + vi.mocked(fsMock.access).mockImplementation(async () => { + throw new Error('ENOENT'); + }); + + setFakeWxt({ + config: { + browser: 'firefox', + manifestVersion: 2, + outDir: '/tmp/wxt-out', + runnerConfig: { + config: {}, + }, + }, + }); + + const runner = createWebExtRunner(); + await expect(runner.openBrowser()).rejects.toThrow( + 'Browser "firefox" not found in WSL', + ); + }); }); diff --git a/packages/wxt/src/core/runners/browser-utils.ts b/packages/wxt/src/core/runners/browser-utils.ts index cfcbb2f18..6da2de897 100644 --- a/packages/wxt/src/core/runners/browser-utils.ts +++ b/packages/wxt/src/core/runners/browser-utils.ts @@ -2,6 +2,7 @@ import * as fs from 'node:fs/promises'; import { constants as fsConstants } from 'node:fs'; import path from 'node:path'; import { wxt } from '../wxt'; +import { KNOWN_BROWSER_PATHS, type KnownTarget } from '@wxt-dev/runner'; /** * Check if a path looks like a Windows path (drive letter, UNC, or WSL mount). @@ -116,3 +117,19 @@ export function sanitizePathForWslWithGui( } return value; } + +/** + * Check if a browser is installed by checking common Linux installation paths. + * Returns the path if found, undefined if not found. + */ +export async function findInstalledBrowser( + browser: string, +): Promise { + const paths = KNOWN_BROWSER_PATHS[browser as KnownTarget]?.linux || []; + for (const browserPath of paths) { + if (await isExecutable(browserPath)) { + return browserPath; + } + } + return undefined; +} diff --git a/packages/wxt/src/core/runners/web-ext.ts b/packages/wxt/src/core/runners/web-ext.ts index 44240493b..662d052da 100644 --- a/packages/wxt/src/core/runners/web-ext.ts +++ b/packages/wxt/src/core/runners/web-ext.ts @@ -5,6 +5,7 @@ import defu from 'defu'; import { wxt } from '../wxt'; import { hasGuiDisplay, isWsl } from '../utils/wsl'; import { + findInstalledBrowser, isWindowsPath, resolveChromiumBinaryForRemoteDebuggingPipe, resolveProfilePath, @@ -67,6 +68,34 @@ export function createWebExtRunner(): ExtensionRunner { loggerPrefix: '[web-ext] ', }); + // Check if browser is installed when running in WSL with GUI + if ( + runningInWslWithGui && + !chromiumBinary && + wxt.config.browser !== 'firefox' + ) { + const foundBinary = await findInstalledBrowser(wxt.config.browser); + if (!foundBinary) { + throw Error( + `Browser "${wxt.config.browser}" not found in WSL. Please install a Linux version of the browser.`, + ); + } + } + + // Check Firefox installation + if ( + runningInWslWithGui && + wxt.config.browser === 'firefox' && + !wxtUserConfig?.binaries?.firefox + ) { + const foundBinary = await findInstalledBrowser('firefox'); + if (!foundBinary) { + throw Error( + `Browser "firefox" not found in WSL. Please install a Linux version of the browser.`, + ); + } + } + const chromiumUserDataDirOverride = wxt.config.browser === 'firefox' ? undefined diff --git a/packages/wxt/src/core/runners/wxt-runner.ts b/packages/wxt/src/core/runners/wxt-runner.ts index 3137aef30..a5cbf654b 100644 --- a/packages/wxt/src/core/runners/wxt-runner.ts +++ b/packages/wxt/src/core/runners/wxt-runner.ts @@ -9,6 +9,7 @@ import { type Runner as WxtRunnerInstance, } from '@wxt-dev/runner'; import { + findInstalledBrowser, resolveChromiumBinaryForRemoteDebuggingPipe, resolveProfilePath, sanitizePathForWslWithGui, @@ -76,6 +77,16 @@ export function createWxtRunner(): ExtensionRunner { }) : binaryFromConfig; + // Check if browser is installed when running in WSL with GUI + if (runningInWslWithGui && !browserBinaryOverride) { + const foundBinary = await findInstalledBrowser(browser); + if (!foundBinary) { + throw Error( + `Browser "${browser}" not found in WSL. Please install a Linux version of the browser.`, + ); + } + } + const startUrls = Array.isArray(userConfig?.startUrls) ? userConfig.startUrls : undefined; From d8bfd23adf000bce332fb3f4c479c7b71337790c Mon Sep 17 00:00:00 2001 From: Seismix Date: Tue, 27 Jan 2026 10:09:44 +0100 Subject: [PATCH 5/7] refactor: simplify wxt-runner by extracting helper functions --- packages/wxt/src/core/runners/wxt-runner.ts | 216 ++++++++++++-------- 1 file changed, 134 insertions(+), 82 deletions(-) diff --git a/packages/wxt/src/core/runners/wxt-runner.ts b/packages/wxt/src/core/runners/wxt-runner.ts index a5cbf654b..61e928af3 100644 --- a/packages/wxt/src/core/runners/wxt-runner.ts +++ b/packages/wxt/src/core/runners/wxt-runner.ts @@ -1,4 +1,4 @@ -import { ExtensionRunner } from '../../types'; +import { ExtensionRunner, WebExtConfig } from '../../types'; import { formatDuration } from '../utils/time'; import { wxt } from '../wxt'; import { hasGuiDisplay } from '../utils/wsl'; @@ -91,88 +91,23 @@ export function createWxtRunner(): ExtensionRunner { ? userConfig.startUrls : undefined; - if (isFirefoxFamilyTarget(browser)) { - const firefoxArgs: string[] = [...(userConfig?.firefoxArgs ?? [])]; - if (startUrls) firefoxArgs.push(...startUrls); - - const firefoxProfile = sanitizePathForWslWithGui( - userConfig?.firefoxProfile, - 'firefoxProfile', - runningInWslWithGui, - '[runner] ', - ); - - const dataPersistence = - firefoxProfile != null || userConfig?.keepProfileChanges - ? 'project' - : 'none'; - const projectDataDir = - firefoxProfile != null - ? resolveProfilePath(wxt.config.root, firefoxProfile) - : undefined; - - const runOptions = { - target: browser, - extensionDir: wxt.config.outDir, - firefoxArgs, - dataPersistence, - projectDataDir, - } as Parameters[0]; - - if (browserBinaryOverride) { - runOptions.browserBinaries = { - [browser]: browserBinaryOverride, - }; - } - - runner = await run(runOptions); - } else { - const chromiumArgs: string[] = [ - '--unsafely-disable-devtools-self-xss-warnings', - ...(userConfig?.chromiumArgs ?? []), - ]; - - if (userConfig?.openDevtools) { - chromiumArgs.push('--auto-open-devtools-for-tabs'); - } - - if (startUrls) { - chromiumArgs.push(...startUrls); - } - - const chromiumProfile = sanitizePathForWslWithGui( - userConfig?.chromiumProfile, - 'chromiumProfile', - runningInWslWithGui, - '[runner] ', - ); - - const dataPersistence = - chromiumProfile != null || userConfig?.keepProfileChanges - ? 'project' - : 'none'; - const projectDataDir = - chromiumProfile != null - ? resolveProfilePath(wxt.config.root, chromiumProfile) - : undefined; - - const runOptions = { - target: browser, - extensionDir: wxt.config.outDir, - chromiumArgs, - chromiumRemoteDebuggingPort: userConfig?.chromiumPort, - dataPersistence, - projectDataDir, - } as Parameters[0]; - - if (browserBinaryOverride) { - runOptions.browserBinaries = { - [browser]: browserBinaryOverride, - }; - } + const runOptions = isFirefoxFamilyTarget(browser) + ? buildFirefoxRunOptions({ + browser, + userConfig, + startUrls, + runningInWslWithGui, + browserBinaryOverride, + }) + : buildChromiumRunOptions({ + browser, + userConfig, + startUrls, + runningInWslWithGui, + browserBinaryOverride, + }); - runner = await run(runOptions); - } + runner = await run(runOptions); const duration = Date.now() - startTime; wxt.logger.success(`Opened browser in ${formatDuration(duration)}`); @@ -184,3 +119,120 @@ export function createWxtRunner(): ExtensionRunner { }, }; } + +function buildFirefoxRunOptions({ + browser, + userConfig, + startUrls, + runningInWslWithGui, + browserBinaryOverride, +}: { + browser: FirefoxFamilyTarget; + userConfig: WebExtConfig | undefined; + startUrls: string[] | undefined; + runningInWslWithGui: boolean; + browserBinaryOverride: string | undefined; +}): Parameters[0] { + const firefoxArgs: string[] = [...(userConfig?.firefoxArgs ?? [])]; + if (startUrls) firefoxArgs.push(...startUrls); + + const firefoxProfile = sanitizePathForWslWithGui( + userConfig?.firefoxProfile, + 'firefoxProfile', + runningInWslWithGui, + '[runner] ', + ); + + const { dataPersistence, projectDataDir } = resolveProfileConfig( + firefoxProfile, + userConfig?.keepProfileChanges, + ); + + const runOptions: Parameters[0] = { + target: browser, + extensionDir: wxt.config.outDir, + firefoxArgs, + dataPersistence, + projectDataDir, + }; + + if (browserBinaryOverride) { + runOptions.browserBinaries = { + [browser]: browserBinaryOverride, + }; + } + + return runOptions; +} + +function buildChromiumRunOptions({ + browser, + userConfig, + startUrls, + runningInWslWithGui, + browserBinaryOverride, +}: { + browser: KnownTarget; + userConfig: WebExtConfig | undefined; + startUrls: string[] | undefined; + runningInWslWithGui: boolean; + browserBinaryOverride: string | undefined; +}): Parameters[0] { + const chromiumArgs: string[] = [ + '--unsafely-disable-devtools-self-xss-warnings', + ...(userConfig?.chromiumArgs ?? []), + ]; + + if (userConfig?.openDevtools) { + chromiumArgs.push('--auto-open-devtools-for-tabs'); + } + + if (startUrls) { + chromiumArgs.push(...startUrls); + } + + const chromiumProfile = sanitizePathForWslWithGui( + userConfig?.chromiumProfile, + 'chromiumProfile', + runningInWslWithGui, + '[runner] ', + ); + + const { dataPersistence, projectDataDir } = resolveProfileConfig( + chromiumProfile, + userConfig?.keepProfileChanges, + ); + + const runOptions: Parameters[0] = { + target: browser, + extensionDir: wxt.config.outDir, + chromiumArgs, + chromiumRemoteDebuggingPort: userConfig?.chromiumPort, + dataPersistence, + projectDataDir, + }; + + if (browserBinaryOverride) { + runOptions.browserBinaries = { + [browser]: browserBinaryOverride, + }; + } + + return runOptions; +} + +function resolveProfileConfig( + profile: string | undefined, + keepProfileChanges: boolean | undefined, +): { + dataPersistence: 'project' | 'none'; + projectDataDir: string | undefined; +} { + const dataPersistence = + profile != null || keepProfileChanges ? 'project' : 'none'; + const projectDataDir = profile + ? resolveProfilePath(wxt.config.root, profile) + : undefined; + + return { dataPersistence, projectDataDir }; +} From 9c5b6e5253a87c48fd8a6d223fde6af57ad0dc10 Mon Sep 17 00:00:00 2001 From: Seismix Date: Tue, 27 Jan 2026 13:10:58 +0100 Subject: [PATCH 6/7] fix: revert accidentaly version bump --- pnpm-lock.yaml | 374 +++++++++++++------------------------------------ 1 file changed, 98 insertions(+), 276 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c70d2890..83ab6336f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,7 +75,7 @@ importers: version: 2.13.1 tsdown: specifier: ^0.18.1 - version: 0.18.4(publint@0.3.16)(typescript@5.9.3) + version: 0.18.1(publint@0.3.16)(typescript@5.9.3) tsx: specifier: 4.21.0 version: 4.21.0 @@ -407,9 +407,6 @@ importers: '@wxt-dev/browser': specifier: workspace:^ version: link:../browser - '@wxt-dev/runner': - specifier: workspace:^ - version: link:../runner '@wxt-dev/storage': specifier: workspace:^1.0.0 version: link:../storage @@ -739,8 +736,8 @@ packages: resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.6': - resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -804,11 +801,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.28.6': - resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} @@ -851,10 +843,6 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.6': - resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} - engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -903,8 +891,8 @@ packages: search-insights: optional: true - '@emnapi/core@1.8.1': - resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} @@ -1396,8 +1384,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@napi-rs/wasm-runtime@1.1.1': - resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@napi-rs/wasm-runtime@1.1.0': + resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1414,9 +1402,6 @@ packages: '@oxc-project/types@0.103.0': resolution: {integrity: sha512-bkiYX5kaXWwUessFRSoXFkGIQTmc6dLGdxuRTrC+h8PSnIdZyuXHHlLAeTmOue5Br/a0/a7dHH0Gca6eXn9MKg==} - '@oxc-project/types@0.110.0': - resolution: {integrity: sha512-6Ct21OIlrEnFEJk5LT4e63pk3btsI6/TusD/GStLi7wYlGJNOl1GI9qvXAnRAxQU9zqA2Oz+UwhfTOU2rPZVow==} - '@oxlint/darwin-arm64@1.33.0': resolution: {integrity: sha512-PmEQDLHAxiAdyttQ1ZWXd+5VpHLbHf3FTMJL9bg5TZamDnhNiW/v0Pamv3MTAdymnoDI3H8IVLAN/SAseV/adw==} cpu: [arm64] @@ -1564,156 +1549,79 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - '@rolldown/binding-android-arm64@1.0.0-beta.57': - resolution: {integrity: sha512-GoOVDy8bjw9z1K30Oo803nSzXJS/vWhFijFsW3kzvZCO8IZwFnNa6pGctmbbJstKl3Fv6UBwyjJQN6msejW0IQ==} + '@rolldown/binding-android-arm64@1.0.0-beta.55': + resolution: {integrity: sha512-5cPpHdO+zp+klznZnIHRO1bMHDq5hS9cqXodEKAaa/dQTPDjnE91OwAsy3o1gT2x4QaY8NzdBXAvutYdaw0WeA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-android-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-He6ZoCfv5D7dlRbrhNBkuMVIHd0GDnjJwbICE1OWpG7G3S2gmJ+eXkcNLJjzjNDpeI2aRy56ou39AJM9AD8YFA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-beta.57': - resolution: {integrity: sha512-9c4FOhRGpl+PX7zBK5p17c5efpF9aSpTPgyigv57hXf5NjQUaJOOiejPLAtFiKNBIfm5Uu6yFkvLKzOafNvlTw==} + '@rolldown/binding-darwin-arm64@1.0.0-beta.55': + resolution: {integrity: sha512-l0887CGU2SXZr0UJmeEcXSvtDCOhDTTYXuoWbhrEJ58YQhQk24EVhDhHMTyjJb1PBRniUgNc1G0T51eF8z+TWw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-YzJdn08kSOXnj85ghHauH2iHpOJ6eSmstdRTLyaziDcUxe9SyQJgGyx/5jDIhDvtOcNvMm2Ju7m19+S/Rm1jFg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-beta.57': - resolution: {integrity: sha512-6RsB8Qy4LnGqNGJJC/8uWeLWGOvbRL/KG5aJ8XXpSEupg/KQtlBEiFaYU/Ma5Usj1s+bt3ItkqZYAI50kSplBA==} + '@rolldown/binding-darwin-x64@1.0.0-beta.55': + resolution: {integrity: sha512-d7qP2AVYzN0tYIP4vJ7nmr26xvmlwdkLD/jWIc9Z9dqh5y0UGPigO3m5eHoHq9BNazmwdD9WzDHbQZyXFZjgtA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.1': - resolution: {integrity: sha512-cIvAbqM+ZVV6lBSKSBtlNqH5iCiW933t1q8j0H66B3sjbe8AxIRetVqfGgcHcJtMzBIkIALlL9fcDrElWLJQcQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-beta.57': - resolution: {integrity: sha512-uA9kG7+MYkHTbqwv67Tx+5GV5YcKd33HCJIi0311iYBd25yuwyIqvJfBdt1VVB8tdOlyTb9cPAgfCki8nhwTQg==} + '@rolldown/binding-freebsd-x64@1.0.0-beta.55': + resolution: {integrity: sha512-j311E4NOB0VMmXHoDDZhrWidUf7L/Sa6bu/+i2cskvHKU40zcUNPSYeD2YiO2MX+hhDFa5bJwhliYfs+bTrSZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-freebsd-x64@1.0.0-rc.1': - resolution: {integrity: sha512-rVt+B1B/qmKwCl1XD02wKfgh3vQPXRXdB/TicV2w6g7RVAM1+cZcpigwhLarqiVCxDObFZ7UgXCxPC7tpDoRog==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.57': - resolution: {integrity: sha512-3KkS0cHsllT2T+Te+VZMKHNw6FPQihYsQh+8J4jkzwgvAQpbsbXmrqhkw3YU/QGRrD8qgcOvBr6z5y6Jid+rmw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': - resolution: {integrity: sha512-69YKwJJBOFprQa1GktPgbuBOfnn+EGxu8sBJ1TjPER+zhSpYeaU4N07uqmyBiksOLGXsMegymuecLobfz03h8Q==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.55': + resolution: {integrity: sha512-lAsaYWhfNTW2A/9O7zCpb5eIJBrFeNEatOS/DDOZ5V/95NHy50g4b/5ViCqchfyFqRb7MKUR18/+xWkIcDkeIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.57': - resolution: {integrity: sha512-A3/wu1RgsHhqP3rVH2+sM81bpk+Qd2XaHTl8LtX5/1LNR7QVBFBCpAoiXwjTdGnI5cMdBVi7Z1pi52euW760Fw==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.55': + resolution: {integrity: sha512-2x6ffiVLZrQv7Xii9+JdtyT1U3bQhKj59K3eRnYlrXsKyjkjfmiDUVx2n+zSyijisUqD62fcegmx2oLLfeTkCA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': - resolution: {integrity: sha512-9JDhHUf3WcLfnViFWm+TyorqUtnSAHaCzlSNmMOq824prVuuzDOK91K0Hl8DUcEb9M5x2O+d2/jmBMsetRIn3g==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.55': + resolution: {integrity: sha512-QbNncvqAXziya5wleI+OJvmceEE15vE4yn4qfbI/hwT/+8ZcqxyfRZOOh62KjisXxp4D0h3JZspycXYejxAU3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.57': - resolution: {integrity: sha512-d0kIVezTQtazpyWjiJIn5to8JlwfKITDqwsFv0Xc6s31N16CD2PC/Pl2OtKgS7n8WLOJbfqgIp5ixYzTAxCqMg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': - resolution: {integrity: sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.57': - resolution: {integrity: sha512-E199LPijo98yrLjPCmETx8EF43sZf9t3guSrLee/ej1rCCc3zDVTR4xFfN9BRAapGVl7/8hYqbbiQPTkv73kUg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.55': + resolution: {integrity: sha512-YZCTZZM+rujxwVc6A+QZaNMJXVtmabmFYLG2VGQTKaBfYGvBKUgtbMEttnp/oZ88BMi2DzadBVhOmfQV8SuHhw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': - resolution: {integrity: sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A==} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.55': + resolution: {integrity: sha512-28q9OQ/DDpFh2keS4BVAlc3N65/wiqKbk5K1pgLdu/uWbKa8hgUJofhXxqO+a+Ya2HVTUuYHneWsI2u+eu3N5Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.57': - resolution: {integrity: sha512-++EQDpk/UJ33kY/BNsh7A7/P1sr/jbMuQ8cE554ZIy+tCUWCivo9zfyjDUoiMdnxqX6HLJEqqGnbGQOvzm2OMQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': - resolution: {integrity: sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-openharmony-arm64@1.0.0-beta.57': - resolution: {integrity: sha512-voDEBcNqxbUv/GeXKFtxXVWA+H45P/8Dec4Ii/SbyJyGvCqV1j+nNHfnFUIiRQ2Q40DwPe/djvgYBs9PpETiMA==} + '@rolldown/binding-openharmony-arm64@1.0.0-beta.55': + resolution: {integrity: sha512-LiCA4BjCnm49B+j1lFzUtlC+4ZphBv0d0g5VqrEJua/uyv9Ey1v9tiaMql1C8c0TVSNDUmrkfHQ71vuQC7YfpQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': - resolution: {integrity: sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.57': - resolution: {integrity: sha512-bRhcF7NLlCnpkzLVlVhrDEd0KH22VbTPkPTbMjlYvqhSmarxNIq5vtlQS8qmV7LkPKHrNLWyJW/V/sOyFba26Q==} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.55': + resolution: {integrity: sha512-nZ76tY7T0Oe8vamz5Cv5CBJvrqeQxwj1WaJ2GxX8Msqs0zsQMMcvoyxOf0glnJlxxgKjtoBxAOxaAU8ERbW6Tg==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': - resolution: {integrity: sha512-2mOxY562ihHlz9lEXuaGEIDCZ1vI+zyFdtsoa3M62xsEunDXQE+DVPO4S4x5MPK9tKulG/aFcA/IH5eVN257Cw==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.57': - resolution: {integrity: sha512-rnDVGRks2FQ2hgJ2g15pHtfxqkGFGjJQUDWzYznEkE8Ra2+Vag9OffxdbJMZqBWXHVM0iS4dv8qSiEn7bO+n1Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': - resolution: {integrity: sha512-oQVOP5cfAWZwRD0Q3nGn/cA9FW3KhMMuQ0NIndALAe6obqjLhqYVYDiGGRGrxvnjJsVbpLwR14gIUYnpIcHR1g==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.55': + resolution: {integrity: sha512-TFVVfLfhL1G+pWspYAgPK/FSqjiBtRKYX9hixfs508QVEZPQlubYAepHPA7kEa6lZXYj5ntzF87KC6RNhxo+ew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.57': - resolution: {integrity: sha512-OqIUyNid1M4xTj6VRXp/Lht/qIP8fo25QyAZlCP+p6D2ATCEhyW4ZIFLnC9zAGN/HMbXoCzvwfa8Jjg/8J4YEg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': - resolution: {integrity: sha512-Ydsxxx++FNOuov3wCBPaYjZrEvKOOGq3k+BF4BPridhg2pENfitSRD2TEuQ8i33bp5VptuNdC9IzxRKU031z5A==} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.55': + resolution: {integrity: sha512-j1WBlk0p+ISgLzMIgl0xHp1aBGXenoK2+qWYc/wil2Vse7kVOdFq9aeQ8ahK6/oxX2teQ5+eDvgjdywqTL+daA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1724,11 +1632,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.34': resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==} - '@rolldown/pluginutils@1.0.0-beta.57': - resolution: {integrity: sha512-aQNelgx14tGA+n2tNSa9x6/jeoCL9fkDeCei7nOKnHx0fEFRRMu5ReiITo+zZD5TzWDGGRjbSYCs93IfRIyTuQ==} - - '@rolldown/pluginutils@1.0.0-rc.1': - resolution: {integrity: sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA==} + '@rolldown/pluginutils@1.0.0-beta.55': + resolution: {integrity: sha512-vajw/B3qoi7aYnnD4BQ4VoCcXQWnF0roSwE2iynbNxgW4l9mFwtLmLmUhpDdcTBfKyZm1p/T0D13qG94XBLohA==} '@rollup/rollup-android-arm-eabi@4.50.0': resolution: {integrity: sha512-lVgpeQyy4fWN5QYebtW4buT/4kn4p4IJ+kDNB4uYNT5b8c8DLJDg6titg20NIg7E8RWwdWZORW6vUFfrLyG3KQ==} @@ -2990,9 +2895,6 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - hookable@6.0.1: - resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} - html-entities@2.3.3: resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} @@ -3024,8 +2926,8 @@ packages: import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - import-without-cache@0.2.5: - resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==} + import-without-cache@0.2.4: + resolution: {integrity: sha512-b/Ke0y4n26ffQhkLvgBxV/NVO/QEE6AZlrMj8DYuxBWNAAu4iMQWZTFWzKcCTEmv7VQ0ae0j8KwrlGzSy8sYQQ==} engines: {node: '>=20.19.0'} inherits@2.0.4: @@ -3892,15 +3794,15 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rolldown-plugin-dts@0.20.0: - resolution: {integrity: sha512-cLAY1kN2ilTYMfZcFlGWbXnu6Nb+8uwUBsi+Mjbh4uIx7IN8uMOmJ7RxrrRgPsO4H7eSz3E+JwGoL1gyugiyUA==} + rolldown-plugin-dts@0.19.1: + resolution: {integrity: sha512-6z501zDTGq6ZrIEdk57qNUwq7kBRGzv3I3SAN2HMJ2KFYjHLnAuPYOmvfiwdxbRZMJ0iMdkV9rYdC3GjurT2cg==} engines: {node: '>=20.19.0'} peerDependencies: '@ts-macro/tsc': ^0.3.6 '@typescript/native-preview': '>=7.0.0-dev.20250601.1' - rolldown: ^1.0.0-beta.57 + rolldown: ^1.0.0-beta.55 typescript: ^5.0.0 - vue-tsc: ~3.2.0 + vue-tsc: ~3.1.0 peerDependenciesMeta: '@ts-macro/tsc': optional: true @@ -3911,13 +3813,8 @@ packages: vue-tsc: optional: true - rolldown@1.0.0-beta.57: - resolution: {integrity: sha512-lMMxcNN71GMsSko8RyeTaFoATHkCh4IWU7pYF73ziMYjhHZWfVesC6GQ+iaJCvZmVjvgSks9Ks1aaqEkBd8udg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rolldown@1.0.0-rc.1: - resolution: {integrity: sha512-M3AeZjYE6UclblEf531Hch0WfVC/NOL43Cc+WdF3J50kk5/fvouHhDumSGTh0oRjbZ8C4faaVr5r6Nx1xMqDGg==} + rolldown@1.0.0-beta.55: + resolution: {integrity: sha512-r8Ws43aYCnfO07ao0SvQRz4TBAtZJjGWNvScRBOHuiNHvjfECOJBIqJv0nUkL1GYcltjvvHswRilDF1ocsC0+g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -4240,8 +4137,8 @@ packages: typescript: optional: true - tsdown@0.18.4: - resolution: {integrity: sha512-J/tRS6hsZTkvqmt4+xdELUCkQYDuUCXgBv0fw3ImV09WPGbEKfsPD65E+WUjSu3E7Z6tji9XZ1iWs8rbGqB/ZA==} + tsdown@0.18.1: + resolution: {integrity: sha512-na4MdVA8QS9Zw++0KovGpjvw1BY5WvoCWcE4Aw0dyfff9nWK8BPzniQEVs+apGUg3DHaYMDfs+XiFaDDgqDDzQ==} engines: {node: '>=20.19.0'} hasBin: true peerDependencies: @@ -4380,8 +4277,8 @@ packages: resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} engines: {node: '>=18.12.0'} - unrun@0.2.26: - resolution: {integrity: sha512-A3DQLBcDyTui4Hlaoojkldg+8x+CIR+tcSHY0wzW+CgB4X/DNyH58jJpXp1B/EkE+yG6tU8iH1mWsLtwFU3IQg==} + unrun@0.2.20: + resolution: {integrity: sha512-YhobStTk93HYRN/4iBs3q3/sd7knvju1XrzwwrVVfRujyTG1K88hGONIxCoJN0PWBuO+BX7fFiHH0sVDfE3MWw==} engines: {node: '>=20.19.0'} hasBin: true peerDependencies: @@ -4871,7 +4768,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.3.0 - tinyexec: 1.0.1 + tinyexec: 1.0.2 '@antfu/utils@9.2.0': {} @@ -4911,10 +4808,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.30 jsesc: 3.1.0 - '@babel/generator@7.28.6': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -4976,10 +4873,6 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@babel/parser@7.28.6': - dependencies: - '@babel/types': 7.28.6 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 @@ -5006,7 +4899,7 @@ snapshots: '@babel/traverse@7.27.7': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/types': 7.28.5 @@ -5018,7 +4911,7 @@ snapshots: '@babel/traverse@7.28.3': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.5 '@babel/template': 7.27.2 @@ -5037,11 +4930,6 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.28.6': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@bcoe/v8-coverage@1.0.2': {} '@commitlint/config-conventional@19.8.1': @@ -5092,7 +4980,7 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@emnapi/core@1.8.1': + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 @@ -5425,9 +5313,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@napi-rs/wasm-runtime@1.1.1': + '@napi-rs/wasm-runtime@1.1.0': dependencies: - '@emnapi/core': 1.8.1 + '@emnapi/core': 1.7.1 '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -5446,8 +5334,6 @@ snapshots: '@oxc-project/types@0.103.0': {} - '@oxc-project/types@0.110.0': {} - '@oxlint/darwin-arm64@1.33.0': optional: true @@ -5557,95 +5443,52 @@ snapshots: dependencies: quansync: 1.0.0 - '@rolldown/binding-android-arm64@1.0.0-beta.57': - optional: true - - '@rolldown/binding-android-arm64@1.0.0-rc.1': + '@rolldown/binding-android-arm64@1.0.0-beta.55': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.57': + '@rolldown/binding-darwin-arm64@1.0.0-beta.55': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.1': + '@rolldown/binding-darwin-x64@1.0.0-beta.55': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.57': + '@rolldown/binding-freebsd-x64@1.0.0-beta.55': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.1': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.55': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.57': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.55': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.1': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.55': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.57': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.55': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.55': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.57': + '@rolldown/binding-openharmony-arm64@1.0.0-beta.55': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.57': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.1': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.57': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.1': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-beta.57': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.1': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-beta.57': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.1': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.57': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.55': dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.1': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.57': + '@napi-rs/wasm-runtime': 1.1.0 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.55': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.57': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.1': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.55': optional: true '@rolldown/pluginutils@1.0.0-beta.29': {} '@rolldown/pluginutils@1.0.0-beta.34': {} - '@rolldown/pluginutils@1.0.0-beta.57': {} - - '@rolldown/pluginutils@1.0.0-rc.1': {} + '@rolldown/pluginutils@1.0.0-beta.55': {} '@rollup/rollup-android-arm-eabi@4.50.0': optional: true @@ -7026,8 +6869,6 @@ snapshots: hookable@5.5.3: {} - hookable@6.0.1: {} - html-entities@2.3.3: {} html-escaper@2.0.2: {} @@ -7053,7 +6894,7 @@ snapshots: import-meta-resolve@4.2.0: {} - import-without-cache@0.2.5: {} + import-without-cache@0.2.4: {} inherits@2.0.4: {} @@ -8046,9 +7887,9 @@ snapshots: rfdc@1.4.1: {} - rolldown-plugin-dts@0.20.0(rolldown@1.0.0-beta.57)(typescript@5.9.3): + rolldown-plugin-dts@0.19.1(rolldown@1.0.0-beta.55)(typescript@5.9.3): dependencies: - '@babel/generator': 7.28.6 + '@babel/generator': 7.28.5 '@babel/parser': 7.28.5 '@babel/types': 7.28.5 ast-kit: 2.2.0 @@ -8056,49 +7897,30 @@ snapshots: dts-resolver: 2.1.3 get-tsconfig: 4.13.0 obug: 2.1.1 - rolldown: 1.0.0-beta.57 + rolldown: 1.0.0-beta.55 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - oxc-resolver - rolldown@1.0.0-beta.57: + rolldown@1.0.0-beta.55: dependencies: '@oxc-project/types': 0.103.0 - '@rolldown/pluginutils': 1.0.0-beta.57 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.57 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.57 - '@rolldown/binding-darwin-x64': 1.0.0-beta.57 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.57 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.57 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.57 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.57 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.57 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.57 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.57 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.57 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.57 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.57 - - rolldown@1.0.0-rc.1: - dependencies: - '@oxc-project/types': 0.110.0 - '@rolldown/pluginutils': 1.0.0-rc.1 + '@rolldown/pluginutils': 1.0.0-beta.55 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.1 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.1 - '@rolldown/binding-darwin-x64': 1.0.0-rc.1 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.1 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.1 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.1 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.1 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.1 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.1 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.1 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.1 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.1 + '@rolldown/binding-android-arm64': 1.0.0-beta.55 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.55 + '@rolldown/binding-darwin-x64': 1.0.0-beta.55 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.55 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.55 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.55 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.55 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.55 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.55 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.55 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.55 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.55 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.55 rollup@4.50.0: dependencies: @@ -8443,24 +8265,24 @@ snapshots: optionalDependencies: typescript: 5.9.3 - tsdown@0.18.4(publint@0.3.16)(typescript@5.9.3): + tsdown@0.18.1(publint@0.3.16)(typescript@5.9.3): dependencies: ansis: 4.2.0 cac: 6.7.14 defu: 6.1.4 empathic: 2.0.0 - hookable: 6.0.1 - import-without-cache: 0.2.5 + hookable: 5.5.3 + import-without-cache: 0.2.4 obug: 2.1.1 picomatch: 4.0.3 - rolldown: 1.0.0-beta.57 - rolldown-plugin-dts: 0.20.0(rolldown@1.0.0-beta.57)(typescript@5.9.3) + rolldown: 1.0.0-beta.55 + rolldown-plugin-dts: 0.19.1(rolldown@1.0.0-beta.55)(typescript@5.9.3) semver: 7.7.3 tinyexec: 1.0.2 tinyglobby: 0.2.15 tree-kill: 1.2.2 unconfig-core: 7.4.2 - unrun: 0.2.26 + unrun: 0.2.20 optionalDependencies: publint: 0.3.16 typescript: 5.9.3 @@ -8616,9 +8438,9 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 - unrun@0.2.26: + unrun@0.2.20: dependencies: - rolldown: 1.0.0-rc.1 + rolldown: 1.0.0-beta.55 update-browserslist-db@1.1.3(browserslist@4.25.4): dependencies: From 038015ec21c990259ac29d0cca7135c6810ec3ce Mon Sep 17 00:00:00 2001 From: Seismix Date: Tue, 27 Jan 2026 13:14:15 +0100 Subject: [PATCH 7/7] fix: revert unintended tsdown version bump in lockfile The lockfile accidentally bumped tsdown from 0.18.1 to 0.18.4, which doesn't properly transpile the `using` keyword, causing build failures. --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83ab6336f..14af4c3fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -407,6 +407,9 @@ importers: '@wxt-dev/browser': specifier: workspace:^ version: link:../browser + '@wxt-dev/runner': + specifier: workspace:^ + version: link:../runner '@wxt-dev/storage': specifier: workspace:^1.0.0 version: link:../storage