diff --git a/src/core/runners/__tests__/index.test.ts b/src/core/runners/__tests__/index.test.ts new file mode 100644 index 000000000..e07911f48 --- /dev/null +++ b/src/core/runners/__tests__/index.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it, vi } from 'vitest'; +import { createExtensionRunner } from '..'; +import { fakeInternalConfig } from '../../../testing/fake-objects'; +import { mock } from 'vitest-mock-extended'; +import { createSafariRunner } from '../safari'; +import { ExtensionRunner } from '../extension-runner'; +import { createWslRunner } from '../wsl'; +import { createManualRunner } from '../manual'; +import { isWsl } from '../../utils/wsl'; +import { createWebExtRunner } from '../web-ext'; + +vi.mock('../../utils/wsl'); +const isWslMock = vi.mocked(isWsl); + +vi.mock('../safari'); +const createSafariRunnerMock = vi.mocked(createSafariRunner); + +vi.mock('../wsl'); +const createWslRunnerMock = vi.mocked(createWslRunner); + +vi.mock('../manual'); +const createManualRunnerMock = vi.mocked(createManualRunner); + +vi.mock('../web-ext'); +const createWebExtRunnerMock = vi.mocked(createWebExtRunner); + +describe('createExtensionRunner', () => { + it('should return a Safari runner when browser is "safari"', async () => { + const config = fakeInternalConfig({ + browser: 'safari', + }); + const safariRunner = mock(); + createSafariRunnerMock.mockReturnValue(safariRunner); + + await expect(createExtensionRunner(config)).resolves.toBe(safariRunner); + }); + + it('should return a WSL runner when `is-wsl` is true', async () => { + isWslMock.mockResolvedValueOnce(true); + const config = fakeInternalConfig({ + browser: 'chrome', + }); + const wslRunner = mock(); + createWslRunnerMock.mockReturnValue(wslRunner); + + await expect(createExtensionRunner(config)).resolves.toBe(wslRunner); + }); + + it('should return a manual runner when `runner.disabled` is true', async () => { + isWslMock.mockResolvedValueOnce(false); + const config = fakeInternalConfig({ + browser: 'chrome', + runnerConfig: { + config: { + disabled: true, + }, + }, + }); + const manualRunner = mock(); + createManualRunnerMock.mockReturnValue(manualRunner); + + await expect(createExtensionRunner(config)).resolves.toBe(manualRunner); + }); + + it('should return a web-ext runner otherwise', async () => { + const config = fakeInternalConfig({ + browser: 'chrome', + runnerConfig: { + config: { + disabled: undefined, + }, + }, + }); + const manualRunner = mock(); + createWebExtRunnerMock.mockReturnValue(manualRunner); + + await expect(createExtensionRunner(config)).resolves.toBe(manualRunner); + }); +}); diff --git a/src/core/runners/index.ts b/src/core/runners/index.ts index ce1043ed3..93fe8dbe9 100644 --- a/src/core/runners/index.ts +++ b/src/core/runners/index.ts @@ -3,14 +3,16 @@ import { ExtensionRunner } from './extension-runner'; import { createWslRunner } from './wsl'; import { createWebExtRunner } from './web-ext'; import { createSafariRunner } from './safari'; +import { createManualRunner } from './manual'; +import { isWsl } from '../utils/wsl'; export async function createExtensionRunner( config: InternalConfig, ): Promise { if (config.browser === 'safari') return createSafariRunner(); - const { default: isWsl } = await import('is-wsl'); // ESM only, requires dynamic import - if (isWsl) return createWslRunner(); + if (await isWsl()) return createWslRunner(); + if (config.runnerConfig.config?.disabled) return createManualRunner(); return createWebExtRunner(); } diff --git a/src/core/runners/manual.ts b/src/core/runners/manual.ts new file mode 100644 index 000000000..5b2dfb842 --- /dev/null +++ b/src/core/runners/manual.ts @@ -0,0 +1,21 @@ +import { ExtensionRunner } from './extension-runner'; +import { relative } from 'node:path'; + +/** + * The manual runner tells the user to load the unpacked extension manually. + */ +export function createManualRunner(): ExtensionRunner { + return { + async openBrowser(config) { + config.logger.info( + `Load "${relative( + process.cwd(), + config.outDir, + )}" as an unpacked extension manually`, + ); + }, + async closeBrowser() { + // noop + }, + }; +} diff --git a/src/core/types/external.ts b/src/core/types/external.ts index aed2b312e..5fc94200c 100644 --- a/src/core/types/external.ts +++ b/src/core/types/external.ts @@ -177,6 +177,8 @@ export interface InlineConfig { /** * Explicitly include bundle analysis when running `wxt build`. This can be overridden by the * command line `--analysis` option. + * + * @default false */ enabled?: boolean; /** @@ -184,6 +186,8 @@ export interface InlineConfig { * bundle will be visualized. See * [`rollup-plugin-visualizer`](https://github.com/btd/rollup-plugin-visualizer#how-to-use-generated-files) * for more details. + * + * @default "treemap" */ template?: PluginVisualizerOptions['template']; }; @@ -474,6 +478,12 @@ export interface ConfigEnv { * Configure how the browser starts up. */ export interface ExtensionRunnerConfig { + /** + * Whether or not to open the browser with the extension installed in dev mode. + * + * @default false + */ + disabled?: boolean; /** * @see https://extensionworkshop.com/documentation/develop/web-ext-command-reference/#browser-console */ diff --git a/src/core/utils/getInternalConfig.ts b/src/core/utils/getInternalConfig.ts index b809790d9..26128a147 100644 --- a/src/core/utils/getInternalConfig.ts +++ b/src/core/utils/getInternalConfig.ts @@ -75,7 +75,8 @@ export async function getInternalConfig( cwd: root, globalRc: true, rcFile: '.webextrc', - overrides: mergedConfig.runner, + overrides: inlineConfig.runner, + defaults: userConfig.runner, }); const finalConfig: InternalConfig = { diff --git a/src/core/utils/wsl.ts b/src/core/utils/wsl.ts new file mode 100644 index 000000000..8b2ce400e --- /dev/null +++ b/src/core/utils/wsl.ts @@ -0,0 +1,7 @@ +/** + * Returns true when running on WSL or WSL2. + */ +export async function isWsl(): Promise { + const { default: isWsl } = await import('is-wsl'); // ESM only, requires dynamic import + return isWsl; +} diff --git a/templates/react/_gitignore b/templates/react/_gitignore index 7903f56e2..442e12e80 100644 --- a/templates/react/_gitignore +++ b/templates/react/_gitignore @@ -11,6 +11,7 @@ node_modules .output stats.html .wxt +web-ext.config.ts # Editor directories and files .vscode/* diff --git a/templates/solid/_gitignore b/templates/solid/_gitignore index 7903f56e2..442e12e80 100644 --- a/templates/solid/_gitignore +++ b/templates/solid/_gitignore @@ -11,6 +11,7 @@ node_modules .output stats.html .wxt +web-ext.config.ts # Editor directories and files .vscode/* diff --git a/templates/svelte/_gitignore b/templates/svelte/_gitignore index 7903f56e2..442e12e80 100644 --- a/templates/svelte/_gitignore +++ b/templates/svelte/_gitignore @@ -11,6 +11,7 @@ node_modules .output stats.html .wxt +web-ext.config.ts # Editor directories and files .vscode/* diff --git a/templates/vanilla/_gitignore b/templates/vanilla/_gitignore index 7903f56e2..442e12e80 100644 --- a/templates/vanilla/_gitignore +++ b/templates/vanilla/_gitignore @@ -11,6 +11,7 @@ node_modules .output stats.html .wxt +web-ext.config.ts # Editor directories and files .vscode/* diff --git a/templates/vue/_gitignore b/templates/vue/_gitignore index 7903f56e2..442e12e80 100644 --- a/templates/vue/_gitignore +++ b/templates/vue/_gitignore @@ -11,6 +11,7 @@ node_modules .output stats.html .wxt +web-ext.config.ts # Editor directories and files .vscode/*