Files
wxt/packages/runner/src/run.ts
T
Aaron b99da14799
📼 VHS / Create VHS (push) Cancelled after 0s
feat(runner): Create new @wxt-dev/runner package (#1566)
2025-06-02 15:01:59 -05:00

126 lines
3.5 KiB
TypeScript

import { debug } from './debug';
import {
resolveRunOptions,
type ResolvedRunOptions,
type RunOptions,
} from './options';
import { spawn } from 'node:child_process';
import { installChromium, installFirefox } from './install';
import { promiseWithResolvers } from './promises';
const debugFirefox = debug.scoped('firefox');
const debugChrome = debug.scoped('chrome');
export interface Runner {
stop(): void;
}
export async function run(options: RunOptions): Promise<Runner> {
const resolvedOptions = await resolveRunOptions(options);
if (
resolvedOptions.target.includes('firefox') ||
resolvedOptions.target.includes('zen')
) {
return runFirefox(resolvedOptions);
} else {
return runChromium(resolvedOptions);
}
}
async function runFirefox(options: ResolvedRunOptions): Promise<Runner> {
const urlRes = promiseWithResolvers<string>();
const urlTimeout = setTimeout(() => {
urlRes.reject(Error('Timed out after 10s waiting for the browser to open'));
}, 10e3);
// Firefox notifies the user if an instance is already running, so we don't add any logs for it.
const browserProcess = spawn(
`"${options.browserBinary}"`,
options.firefoxArgs,
{
stdio: ['ignore', 'pipe', 'pipe'],
shell: true,
},
);
const debugFirefoxStderr = debugFirefox.scoped('stderr');
browserProcess.stderr.on('data', (data: string) => {
const message = data.toString().trim();
debugFirefoxStderr(message);
if (message.startsWith('WebDriver BiDi listening on ws://')) {
clearTimeout(urlTimeout);
urlRes.resolve(message.slice(28));
}
});
const debugFirefoxStdout = debugFirefox.scoped('stdout');
browserProcess.stdout.on('data', (data: string) => {
const message = data.toString().trim();
debugFirefoxStdout(message);
});
const baseUrl = await urlRes.promise;
await installFirefox(baseUrl, options.extensionDir);
return {
stop() {
browserProcess.kill('SIGINT');
},
};
}
async function runChromium(options: ResolvedRunOptions): Promise<Runner> {
const browserProcess = spawn(
`"${options.browserBinary}"`,
options.chromiumArgs,
{
stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'],
shell: true,
},
);
const opened = promiseWithResolvers<void>();
const openedTimeout = setTimeout(() => {
opened.reject(Error('Timed out after 10s waiting for browser to open.'));
}, 10e3);
const debugChromeStderr = debugChrome.scoped('stderr');
browserProcess.stderr!.on('data', (data: string) => {
const message = data.toString().trim();
debugChromeStderr(message);
// This message signifies Chrome started up correctly.
if (message.startsWith('DevTools listening on')) {
clearTimeout(openedTimeout);
opened.resolve();
}
});
const debugChromeStdout = debugChrome.scoped('stdout');
browserProcess.stdout!.on('data', (data: string) => {
const message = data.toString().trim();
debugChromeStdout(message);
// This message signifies Chrome was already open, and thus we couldn't open the required new instance.
if (message === 'Opening in existing browser session.') {
clearTimeout(openedTimeout);
opened.reject(
Error(
'An instance of the browser is already running. Close it and try again.',
),
);
}
});
// Wait for the browser to open before proceeding.
await opened.promise;
await installChromium(browserProcess, options.extensionDir);
return {
stop() {
browserProcess.kill('SIGINT');
},
};
}