Update dashboard, kb, memory +4 more (+28 ~18 -1)
This commit is contained in:
558
node_modules/@puppeteer/browsers/src/CLI.ts
generated
vendored
Normal file
558
node_modules/@puppeteer/browsers/src/CLI.ts
generated
vendored
Normal file
@@ -0,0 +1,558 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {stdin as input, stdout as output} from 'node:process';
|
||||
import * as readline from 'node:readline';
|
||||
|
||||
import type * as Yargs from 'yargs';
|
||||
|
||||
import {
|
||||
Browser,
|
||||
resolveBuildId,
|
||||
BrowserPlatform,
|
||||
type ChromeReleaseChannel,
|
||||
} from './browser-data/browser-data.js';
|
||||
import {Cache} from './Cache.js';
|
||||
import {detectBrowserPlatform} from './detectPlatform.js';
|
||||
import {install} from './install.js';
|
||||
import {
|
||||
computeExecutablePath,
|
||||
computeSystemExecutablePath,
|
||||
launch,
|
||||
} from './launch.js';
|
||||
|
||||
interface InstallBrowser {
|
||||
name: Browser;
|
||||
buildId: string;
|
||||
}
|
||||
interface InstallArgs {
|
||||
browser?: InstallBrowser;
|
||||
path?: string;
|
||||
platform?: BrowserPlatform;
|
||||
baseUrl?: string;
|
||||
installDeps?: boolean;
|
||||
}
|
||||
|
||||
function isValidBrowser(browser: unknown): browser is Browser {
|
||||
return Object.values(Browser).includes(browser as Browser);
|
||||
}
|
||||
|
||||
function isValidPlatform(platform: unknown): platform is BrowserPlatform {
|
||||
return Object.values(BrowserPlatform).includes(platform as BrowserPlatform);
|
||||
}
|
||||
|
||||
// If moved update release-please config
|
||||
// x-release-please-start-version
|
||||
const packageVersion = '2.12.0';
|
||||
// x-release-please-end
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class CLI {
|
||||
#cachePath: string;
|
||||
#rl?: readline.Interface;
|
||||
#scriptName: string;
|
||||
#version: string;
|
||||
#allowCachePathOverride: boolean;
|
||||
#pinnedBrowsers?: Partial<
|
||||
Record<
|
||||
Browser,
|
||||
{
|
||||
buildId: string;
|
||||
skipDownload: boolean;
|
||||
}
|
||||
>
|
||||
>;
|
||||
#prefixCommand?: {cmd: string; description: string};
|
||||
|
||||
constructor(
|
||||
opts?:
|
||||
| string
|
||||
| {
|
||||
cachePath?: string;
|
||||
scriptName?: string;
|
||||
version?: string;
|
||||
prefixCommand?: {cmd: string; description: string};
|
||||
allowCachePathOverride?: boolean;
|
||||
pinnedBrowsers?: Partial<
|
||||
Record<
|
||||
Browser,
|
||||
{
|
||||
buildId: string;
|
||||
skipDownload: boolean;
|
||||
}
|
||||
>
|
||||
>;
|
||||
},
|
||||
rl?: readline.Interface,
|
||||
) {
|
||||
if (!opts) {
|
||||
opts = {};
|
||||
}
|
||||
if (typeof opts === 'string') {
|
||||
opts = {
|
||||
cachePath: opts,
|
||||
};
|
||||
}
|
||||
this.#cachePath = opts.cachePath ?? process.cwd();
|
||||
this.#rl = rl;
|
||||
this.#scriptName = opts.scriptName ?? '@puppeteer/browsers';
|
||||
this.#version = opts.version ?? packageVersion;
|
||||
this.#allowCachePathOverride = opts.allowCachePathOverride ?? true;
|
||||
this.#pinnedBrowsers = opts.pinnedBrowsers;
|
||||
this.#prefixCommand = opts.prefixCommand;
|
||||
}
|
||||
|
||||
#defineBrowserParameter<T>(
|
||||
yargs: Yargs.Argv<T>,
|
||||
required: true,
|
||||
): Yargs.Argv<T & {browser: InstallBrowser}>;
|
||||
#defineBrowserParameter<T>(
|
||||
yargs: Yargs.Argv<T>,
|
||||
required: boolean,
|
||||
): Yargs.Argv<T & {browser: InstallBrowser | undefined}>;
|
||||
#defineBrowserParameter<T>(yargs: Yargs.Argv<T>, required: boolean) {
|
||||
return yargs.positional('browser', {
|
||||
description:
|
||||
'Which browser to install <browser>[@<buildId|latest>]. `latest` will try to find the latest available build. `buildId` is a browser-specific identifier such as a version or a revision.',
|
||||
type: 'string',
|
||||
coerce: (opt): InstallBrowser => {
|
||||
const browser: InstallBrowser = {
|
||||
name: this.#parseBrowser(opt),
|
||||
buildId: this.#parseBuildId(opt),
|
||||
};
|
||||
|
||||
if (!isValidBrowser(browser.name)) {
|
||||
throw new Error(`Unsupported browser '${browser.name}'`);
|
||||
}
|
||||
|
||||
return browser;
|
||||
},
|
||||
demandOption: required,
|
||||
});
|
||||
}
|
||||
|
||||
#definePlatformParameter<T>(yargs: Yargs.Argv<T>) {
|
||||
return yargs.option('platform', {
|
||||
type: 'string',
|
||||
desc: 'Platform that the binary needs to be compatible with.',
|
||||
choices: Object.values(BrowserPlatform),
|
||||
default: detectBrowserPlatform(),
|
||||
coerce: platform => {
|
||||
if (!isValidPlatform(platform)) {
|
||||
throw new Error(`Unsupported platform '${platform}'`);
|
||||
}
|
||||
|
||||
return platform;
|
||||
},
|
||||
defaultDescription: 'Auto-detected',
|
||||
});
|
||||
}
|
||||
|
||||
#definePathParameter<T>(yargs: Yargs.Argv<T>, required = false) {
|
||||
if (!this.#allowCachePathOverride) {
|
||||
return yargs as Yargs.Argv<T & {path: undefined}>;
|
||||
}
|
||||
|
||||
return yargs.option('path', {
|
||||
type: 'string',
|
||||
desc: 'Path to the root folder for the browser downloads and installation. If a relative path is provided, it will be resolved relative to the current working directory. The installation folder structure is compatible with the cache structure used by Puppeteer.',
|
||||
defaultDescription: 'Current working directory',
|
||||
...(required ? {} : {default: process.cwd()}),
|
||||
demandOption: required,
|
||||
});
|
||||
}
|
||||
|
||||
async run(argv: string[]): Promise<void> {
|
||||
const {default: yargs} = await import('yargs');
|
||||
const {hideBin} = await import('yargs/helpers');
|
||||
const yargsInstance = yargs(hideBin(argv));
|
||||
let target = yargsInstance
|
||||
.scriptName(this.#scriptName)
|
||||
.version(this.#version);
|
||||
if (this.#prefixCommand) {
|
||||
target = target.command(
|
||||
this.#prefixCommand.cmd,
|
||||
this.#prefixCommand.description,
|
||||
yargs => {
|
||||
return this.#build(yargs);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
target = this.#build(target);
|
||||
}
|
||||
await target
|
||||
.demandCommand(1)
|
||||
.help()
|
||||
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|
||||
.parseAsync();
|
||||
}
|
||||
|
||||
#build(yargs: Yargs.Argv<unknown>) {
|
||||
const latestOrPinned = this.#pinnedBrowsers ? 'pinned' : 'latest';
|
||||
// If there are pinned browsers allow the positional arg to be optional
|
||||
const browserArgType = this.#pinnedBrowsers ? '[browser]' : '<browser>';
|
||||
return yargs
|
||||
.command(
|
||||
`install ${browserArgType}`,
|
||||
'Download and install the specified browser. If successful, the command outputs the actual browser buildId that was installed and the absolute path to the browser executable (format: <browser>@<buildID> <path>).',
|
||||
yargs => {
|
||||
if (this.#pinnedBrowsers) {
|
||||
yargs.example('$0 install', 'Install all pinned browsers');
|
||||
}
|
||||
yargs
|
||||
.example(
|
||||
'$0 install chrome',
|
||||
`Install the ${latestOrPinned} available build of the Chrome browser.`,
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome@latest',
|
||||
'Install the latest available build for the Chrome browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome@stable',
|
||||
'Install the latest available build for the Chrome browser from the stable channel.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome@beta',
|
||||
'Install the latest available build for the Chrome browser from the beta channel.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome@dev',
|
||||
'Install the latest available build for the Chrome browser from the dev channel.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome@canary',
|
||||
'Install the latest available build for the Chrome Canary browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome@115',
|
||||
'Install the latest available build for Chrome 115.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chromedriver@canary',
|
||||
'Install the latest available build for ChromeDriver Canary.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chromedriver@115',
|
||||
'Install the latest available build for ChromeDriver 115.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chromedriver@115.0.5790',
|
||||
'Install the latest available patch (115.0.5790.X) build for ChromeDriver.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome-headless-shell',
|
||||
'Install the latest available chrome-headless-shell build.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome-headless-shell@beta',
|
||||
'Install the latest available chrome-headless-shell build corresponding to the Beta channel.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chrome-headless-shell@118',
|
||||
'Install the latest available chrome-headless-shell 118 build.',
|
||||
)
|
||||
.example(
|
||||
'$0 install chromium@1083080',
|
||||
'Install the revision 1083080 of the Chromium browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install firefox',
|
||||
'Install the latest nightly available build of the Firefox browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install firefox@stable',
|
||||
'Install the latest stable build of the Firefox browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install firefox@beta',
|
||||
'Install the latest beta build of the Firefox browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install firefox@devedition',
|
||||
'Install the latest devedition build of the Firefox browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install firefox@esr',
|
||||
'Install the latest ESR build of the Firefox browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install firefox@nightly',
|
||||
'Install the latest nightly build of the Firefox browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install firefox@stable_111.0.1',
|
||||
'Install a specific version of the Firefox browser.',
|
||||
)
|
||||
.example(
|
||||
'$0 install firefox --platform mac',
|
||||
'Install the latest Mac (Intel) build of the Firefox browser.',
|
||||
);
|
||||
if (this.#allowCachePathOverride) {
|
||||
yargs.example(
|
||||
'$0 install firefox --path /tmp/my-browser-cache',
|
||||
'Install to the specified cache directory.',
|
||||
);
|
||||
}
|
||||
|
||||
const yargsWithBrowserParam = this.#defineBrowserParameter(
|
||||
yargs,
|
||||
!Boolean(this.#pinnedBrowsers),
|
||||
);
|
||||
const yargsWithPlatformParam = this.#definePlatformParameter(
|
||||
yargsWithBrowserParam,
|
||||
);
|
||||
return this.#definePathParameter(yargsWithPlatformParam, false)
|
||||
.option('base-url', {
|
||||
type: 'string',
|
||||
desc: 'Base URL to download from',
|
||||
})
|
||||
.option('install-deps', {
|
||||
type: 'boolean',
|
||||
desc: 'Whether to attempt installing system dependencies (only supported on Linux, requires root privileges).',
|
||||
default: false,
|
||||
});
|
||||
},
|
||||
async args => {
|
||||
if (this.#pinnedBrowsers && !args.browser) {
|
||||
// Use allSettled to avoid scenarios that
|
||||
// a browser may fail early and leave the other
|
||||
// installation in a faulty state
|
||||
const result = await Promise.allSettled(
|
||||
Object.entries(this.#pinnedBrowsers).map(
|
||||
async ([browser, options]) => {
|
||||
if (options.skipDownload) {
|
||||
return;
|
||||
}
|
||||
await this.#install({
|
||||
...args,
|
||||
browser: {
|
||||
name: browser as Browser,
|
||||
buildId: options.buildId,
|
||||
},
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
for (const install of result) {
|
||||
if (install.status === 'rejected') {
|
||||
throw install.reason;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await this.#install(args);
|
||||
}
|
||||
},
|
||||
)
|
||||
.command(
|
||||
'launch <browser>',
|
||||
'Launch the specified browser',
|
||||
yargs => {
|
||||
yargs
|
||||
.example(
|
||||
'$0 launch chrome@115.0.5790.170',
|
||||
'Launch Chrome 115.0.5790.170',
|
||||
)
|
||||
.example(
|
||||
'$0 launch firefox@112.0a1',
|
||||
'Launch the Firefox browser identified by the milestone 112.0a1.',
|
||||
)
|
||||
.example(
|
||||
'$0 launch chrome@115.0.5790.170 --detached',
|
||||
'Launch the browser but detach the sub-processes.',
|
||||
)
|
||||
.example(
|
||||
'$0 launch chrome@canary --system',
|
||||
'Try to locate the Canary build of Chrome installed on the system and launch it.',
|
||||
)
|
||||
.example(
|
||||
'$0 launch chrome@115.0.5790.170 -- --version',
|
||||
'Launch Chrome 115.0.5790.170 and pass custom argument to the binary.',
|
||||
);
|
||||
|
||||
const yargsWithExtraAgs = yargs.parserConfiguration({
|
||||
'populate--': true,
|
||||
// Yargs does not have the correct overload for this.
|
||||
}) as Yargs.Argv<{'--'?: Array<string | number>}>;
|
||||
const yargsWithBrowserParam = this.#defineBrowserParameter(
|
||||
yargsWithExtraAgs,
|
||||
true,
|
||||
);
|
||||
const yargsWithPlatformParam = this.#definePlatformParameter(
|
||||
yargsWithBrowserParam,
|
||||
);
|
||||
return this.#definePathParameter(yargsWithPlatformParam)
|
||||
.option('detached', {
|
||||
type: 'boolean',
|
||||
desc: 'Detach the child process.',
|
||||
default: false,
|
||||
})
|
||||
.option('system', {
|
||||
type: 'boolean',
|
||||
desc: 'Search for a browser installed on the system instead of the cache folder.',
|
||||
default: false,
|
||||
})
|
||||
.option('dumpio', {
|
||||
type: 'boolean',
|
||||
desc: "Forwards the browser's process stdout and stderr",
|
||||
default: false,
|
||||
});
|
||||
},
|
||||
async args => {
|
||||
const extraArgs = args['--']?.filter(arg => {
|
||||
return typeof arg === 'string';
|
||||
});
|
||||
|
||||
args.browser.buildId = this.#resolvePinnedBrowserIfNeeded(
|
||||
args.browser.buildId,
|
||||
args.browser.name,
|
||||
);
|
||||
|
||||
const executablePath = args.system
|
||||
? computeSystemExecutablePath({
|
||||
browser: args.browser.name,
|
||||
// TODO: throw an error if not a ChromeReleaseChannel is provided.
|
||||
channel: args.browser.buildId as ChromeReleaseChannel,
|
||||
platform: args.platform,
|
||||
})
|
||||
: computeExecutablePath({
|
||||
browser: args.browser.name,
|
||||
buildId: args.browser.buildId,
|
||||
cacheDir: args.path ?? this.#cachePath,
|
||||
platform: args.platform,
|
||||
});
|
||||
launch({
|
||||
args: extraArgs,
|
||||
executablePath,
|
||||
dumpio: args.dumpio,
|
||||
detached: args.detached,
|
||||
});
|
||||
},
|
||||
)
|
||||
.command(
|
||||
'clear',
|
||||
this.#allowCachePathOverride
|
||||
? 'Removes all installed browsers from the specified cache directory'
|
||||
: `Removes all installed browsers from ${this.#cachePath}`,
|
||||
yargs => {
|
||||
return this.#definePathParameter(yargs, true);
|
||||
},
|
||||
async args => {
|
||||
const cacheDir = args.path ?? this.#cachePath;
|
||||
const rl = this.#rl ?? readline.createInterface({input, output});
|
||||
rl.question(
|
||||
`Do you want to permanently and recursively delete the content of ${cacheDir} (yes/No)? `,
|
||||
answer => {
|
||||
rl.close();
|
||||
if (!['y', 'yes'].includes(answer.toLowerCase().trim())) {
|
||||
console.log('Cancelled.');
|
||||
return;
|
||||
}
|
||||
const cache = new Cache(cacheDir);
|
||||
cache.clear();
|
||||
console.log(`${cacheDir} cleared.`);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.command(
|
||||
'list',
|
||||
'List all installed browsers in the cache directory',
|
||||
yargs => {
|
||||
yargs.example(
|
||||
'$0 list',
|
||||
'List all installed browsers in the cache directory',
|
||||
);
|
||||
if (this.#allowCachePathOverride) {
|
||||
yargs.example(
|
||||
'$0 list --path /tmp/my-browser-cache',
|
||||
'List browsers installed in the specified cache directory',
|
||||
);
|
||||
}
|
||||
|
||||
return this.#definePathParameter(yargs);
|
||||
},
|
||||
async args => {
|
||||
const cacheDir = args.path ?? this.#cachePath;
|
||||
const cache = new Cache(cacheDir);
|
||||
const browsers = cache.getInstalledBrowsers();
|
||||
|
||||
for (const browser of browsers) {
|
||||
console.log(
|
||||
`${browser.browser}@${browser.buildId} (${browser.platform}) ${browser.executablePath}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
.demandCommand(1)
|
||||
.help();
|
||||
}
|
||||
|
||||
#parseBrowser(version: string): Browser {
|
||||
return version.split('@').shift() as Browser;
|
||||
}
|
||||
|
||||
#parseBuildId(version: string): string {
|
||||
const parts = version.split('@');
|
||||
return parts.length === 2
|
||||
? parts[1]!
|
||||
: this.#pinnedBrowsers
|
||||
? 'pinned'
|
||||
: 'latest';
|
||||
}
|
||||
|
||||
#resolvePinnedBrowserIfNeeded(buildId: string, browserName: Browser): string {
|
||||
if (buildId === 'pinned') {
|
||||
const options = this.#pinnedBrowsers?.[browserName];
|
||||
if (!options || !options.buildId) {
|
||||
throw new Error(`No pinned version found for ${browserName}`);
|
||||
}
|
||||
return options.buildId;
|
||||
}
|
||||
return buildId;
|
||||
}
|
||||
|
||||
async #install(args: InstallArgs) {
|
||||
if (!args.browser) {
|
||||
throw new Error(`No browser arg provided`);
|
||||
}
|
||||
if (!args.platform) {
|
||||
throw new Error(`Could not resolve the current platform`);
|
||||
}
|
||||
args.browser.buildId = this.#resolvePinnedBrowserIfNeeded(
|
||||
args.browser.buildId,
|
||||
args.browser.name,
|
||||
);
|
||||
const originalBuildId = args.browser.buildId;
|
||||
args.browser.buildId = await resolveBuildId(
|
||||
args.browser.name,
|
||||
args.platform,
|
||||
args.browser.buildId,
|
||||
);
|
||||
await install({
|
||||
browser: args.browser.name,
|
||||
buildId: args.browser.buildId,
|
||||
platform: args.platform,
|
||||
cacheDir: args.path ?? this.#cachePath,
|
||||
downloadProgressCallback: 'default',
|
||||
baseUrl: args.baseUrl,
|
||||
buildIdAlias:
|
||||
originalBuildId !== args.browser.buildId ? originalBuildId : undefined,
|
||||
installDeps: args.installDeps,
|
||||
});
|
||||
console.log(
|
||||
`${args.browser.name}@${args.browser.buildId} ${computeExecutablePath({
|
||||
browser: args.browser.name,
|
||||
buildId: args.browser.buildId,
|
||||
cacheDir: args.path ?? this.#cachePath,
|
||||
platform: args.platform,
|
||||
})}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
321
node_modules/@puppeteer/browsers/src/Cache.ts
generated
vendored
Normal file
321
node_modules/@puppeteer/browsers/src/Cache.ts
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import debug from 'debug';
|
||||
|
||||
import {
|
||||
Browser,
|
||||
type BrowserPlatform,
|
||||
executablePathByBrowser,
|
||||
getVersionComparator,
|
||||
} from './browser-data/browser-data.js';
|
||||
import {detectBrowserPlatform} from './detectPlatform.js';
|
||||
|
||||
const debugCache = debug('puppeteer:browsers:cache');
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class InstalledBrowser {
|
||||
browser: Browser;
|
||||
buildId: string;
|
||||
platform: BrowserPlatform;
|
||||
readonly executablePath: string;
|
||||
|
||||
#cache: Cache;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(
|
||||
cache: Cache,
|
||||
browser: Browser,
|
||||
buildId: string,
|
||||
platform: BrowserPlatform,
|
||||
) {
|
||||
this.#cache = cache;
|
||||
this.browser = browser;
|
||||
this.buildId = buildId;
|
||||
this.platform = platform;
|
||||
this.executablePath = cache.computeExecutablePath({
|
||||
browser,
|
||||
buildId,
|
||||
platform,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to the root of the installation folder. Use
|
||||
* {@link computeExecutablePath} to get the path to the executable binary.
|
||||
*/
|
||||
get path(): string {
|
||||
return this.#cache.installationDir(
|
||||
this.browser,
|
||||
this.platform,
|
||||
this.buildId,
|
||||
);
|
||||
}
|
||||
|
||||
readMetadata(): Metadata {
|
||||
return this.#cache.readMetadata(this.browser);
|
||||
}
|
||||
|
||||
writeMetadata(metadata: Metadata): void {
|
||||
this.#cache.writeMetadata(this.browser, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface ComputeExecutablePathOptions {
|
||||
/**
|
||||
* Determines which platform the browser will be suited for.
|
||||
*
|
||||
* @defaultValue **Auto-detected.**
|
||||
*/
|
||||
platform?: BrowserPlatform;
|
||||
/**
|
||||
* Determines which browser to launch.
|
||||
*/
|
||||
browser: Browser;
|
||||
/**
|
||||
* Determines which buildId to download. BuildId should uniquely identify
|
||||
* binaries and they are used for caching.
|
||||
*/
|
||||
buildId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Metadata {
|
||||
// Maps an alias (canary/latest/dev/etc.) to a buildId.
|
||||
aliases: Record<string, string>;
|
||||
// Maps installation key (platform-buildId) to executable path.
|
||||
executablePaths?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache used by Puppeteer relies on the following structure:
|
||||
*
|
||||
* - rootDir
|
||||
* -- <browser1> | browserRoot(browser1)
|
||||
* ---- <platform>-<buildId> | installationDir()
|
||||
* ------ the browser-platform-buildId
|
||||
* ------ specific structure.
|
||||
* -- <browser2> | browserRoot(browser2)
|
||||
* ---- <platform>-<buildId> | installationDir()
|
||||
* ------ the browser-platform-buildId
|
||||
* ------ specific structure.
|
||||
* @internal
|
||||
*/
|
||||
export class Cache {
|
||||
#rootDir: string;
|
||||
|
||||
constructor(rootDir: string) {
|
||||
this.#rootDir = rootDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get rootDir(): string {
|
||||
return this.#rootDir;
|
||||
}
|
||||
|
||||
browserRoot(browser: Browser): string {
|
||||
return path.join(this.#rootDir, browser);
|
||||
}
|
||||
|
||||
metadataFile(browser: Browser): string {
|
||||
return path.join(this.browserRoot(browser), '.metadata');
|
||||
}
|
||||
|
||||
readMetadata(browser: Browser): Metadata {
|
||||
const metatadaPath = this.metadataFile(browser);
|
||||
if (!fs.existsSync(metatadaPath)) {
|
||||
return {aliases: {}};
|
||||
}
|
||||
// TODO: add type-safe parsing.
|
||||
const data = JSON.parse(fs.readFileSync(metatadaPath, 'utf8'));
|
||||
if (typeof data !== 'object') {
|
||||
throw new Error('.metadata is not an object');
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
writeMetadata(browser: Browser, metadata: Metadata): void {
|
||||
const metatadaPath = this.metadataFile(browser);
|
||||
fs.mkdirSync(path.dirname(metatadaPath), {recursive: true});
|
||||
fs.writeFileSync(metatadaPath, JSON.stringify(metadata, null, 2));
|
||||
}
|
||||
|
||||
readExecutablePath(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): string | null {
|
||||
const metadata = this.readMetadata(browser);
|
||||
const key = `${platform}-${buildId}`;
|
||||
return metadata.executablePaths?.[key] ?? null;
|
||||
}
|
||||
|
||||
writeExecutablePath(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
executablePath: string,
|
||||
): void {
|
||||
const metadata = this.readMetadata(browser);
|
||||
if (!metadata.executablePaths) {
|
||||
metadata.executablePaths = {};
|
||||
}
|
||||
const key = `${platform}-${buildId}`;
|
||||
metadata.executablePaths[key] = executablePath;
|
||||
this.writeMetadata(browser, metadata);
|
||||
}
|
||||
|
||||
resolveAlias(browser: Browser, alias: string): string | undefined {
|
||||
const metadata = this.readMetadata(browser);
|
||||
if (alias === 'latest') {
|
||||
return Object.values(metadata.aliases || {})
|
||||
.sort(getVersionComparator(browser))
|
||||
.at(-1);
|
||||
}
|
||||
return metadata.aliases[alias];
|
||||
}
|
||||
|
||||
installationDir(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): string {
|
||||
return path.join(this.browserRoot(browser), `${platform}-${buildId}`);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
fs.rmSync(this.#rootDir, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
maxRetries: 10,
|
||||
retryDelay: 500,
|
||||
});
|
||||
}
|
||||
|
||||
uninstall(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): void {
|
||||
const metadata = this.readMetadata(browser);
|
||||
for (const alias of Object.keys(metadata.aliases)) {
|
||||
if (metadata.aliases[alias] === buildId) {
|
||||
delete metadata.aliases[alias];
|
||||
}
|
||||
}
|
||||
// Clean up executable path entry
|
||||
const key = `${platform}-${buildId}`;
|
||||
if (metadata.executablePaths?.[key]) {
|
||||
delete metadata.executablePaths[key];
|
||||
this.writeMetadata(browser, metadata);
|
||||
}
|
||||
fs.rmSync(this.installationDir(browser, platform, buildId), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
maxRetries: 10,
|
||||
retryDelay: 500,
|
||||
});
|
||||
}
|
||||
|
||||
getInstalledBrowsers(): InstalledBrowser[] {
|
||||
if (!fs.existsSync(this.#rootDir)) {
|
||||
return [];
|
||||
}
|
||||
const types = fs.readdirSync(this.#rootDir);
|
||||
const browsers = types.filter((t): t is Browser => {
|
||||
return (Object.values(Browser) as string[]).includes(t);
|
||||
});
|
||||
return browsers.flatMap(browser => {
|
||||
const files = fs.readdirSync(this.browserRoot(browser));
|
||||
return files
|
||||
.map(file => {
|
||||
const result = parseFolderPath(
|
||||
path.join(this.browserRoot(browser), file),
|
||||
);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
return new InstalledBrowser(
|
||||
this,
|
||||
browser,
|
||||
result.buildId,
|
||||
result.platform as BrowserPlatform,
|
||||
);
|
||||
})
|
||||
.filter((item: InstalledBrowser | null): item is InstalledBrowser => {
|
||||
return item !== null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
computeExecutablePath(options: ComputeExecutablePathOptions): string {
|
||||
options.platform ??= detectBrowserPlatform();
|
||||
if (!options.platform) {
|
||||
throw new Error(
|
||||
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`,
|
||||
);
|
||||
}
|
||||
try {
|
||||
options.buildId =
|
||||
this.resolveAlias(options.browser, options.buildId) ?? options.buildId;
|
||||
} catch {
|
||||
debugCache('could not read .metadata file for the browser');
|
||||
}
|
||||
const installationDir = this.installationDir(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.buildId,
|
||||
);
|
||||
|
||||
const storedExecutablePath = this.readExecutablePath(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.buildId,
|
||||
);
|
||||
if (storedExecutablePath) {
|
||||
// The metadata contains a resolved relative path from the installation dir
|
||||
return path.join(installationDir, storedExecutablePath);
|
||||
}
|
||||
|
||||
return path.join(
|
||||
installationDir,
|
||||
executablePathByBrowser[options.browser](
|
||||
options.platform,
|
||||
options.buildId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function parseFolderPath(
|
||||
folderPath: string,
|
||||
): {platform: string; buildId: string} | undefined {
|
||||
const name = path.basename(folderPath);
|
||||
const splits = name.split('-');
|
||||
if (splits.length !== 2) {
|
||||
return;
|
||||
}
|
||||
const [platform, buildId] = splits;
|
||||
if (!buildId || !platform) {
|
||||
return;
|
||||
}
|
||||
return {platform, buildId};
|
||||
}
|
||||
91
node_modules/@puppeteer/browsers/src/DefaultProvider.spec.ts
generated
vendored
Normal file
91
node_modules/@puppeteer/browsers/src/DefaultProvider.spec.ts
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import assert from 'node:assert';
|
||||
|
||||
import {Browser, BrowserPlatform, DefaultProvider} from './main.js';
|
||||
|
||||
describe('DefaultProvider', () => {
|
||||
let provider: DefaultProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
provider = new DefaultProvider();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create provider with default base URL', () => {
|
||||
const defaultProvider = new DefaultProvider();
|
||||
assert(defaultProvider instanceof DefaultProvider);
|
||||
});
|
||||
|
||||
it('should create provider with custom base URL', () => {
|
||||
const customBaseUrl = 'https://custom.example.com/';
|
||||
const customProvider = new DefaultProvider(customBaseUrl);
|
||||
assert(customProvider instanceof DefaultProvider);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BrowserProvider interface compliance', () => {
|
||||
it('should implement supports method', () => {
|
||||
assert.strictEqual(typeof provider.supports, 'function');
|
||||
});
|
||||
|
||||
it('should implement getDownloadUrl method', () => {
|
||||
assert.strictEqual(typeof provider.getDownloadUrl, 'function');
|
||||
});
|
||||
|
||||
it('should implement getExecutablePath method', () => {
|
||||
assert.strictEqual(typeof provider.getExecutablePath, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('basic functionality', () => {
|
||||
it('should handle different browsers', () => {
|
||||
// Test with a known build ID that should exist
|
||||
const result = provider.supports({
|
||||
browser: Browser.CHROME,
|
||||
platform: BrowserPlatform.LINUX,
|
||||
buildId: '120.0.6099.109',
|
||||
});
|
||||
|
||||
// Chrome for Testing supports all browsers
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
|
||||
it('should handle different platforms', () => {
|
||||
const result = provider.supports({
|
||||
browser: Browser.CHROME,
|
||||
platform: BrowserPlatform.MAC,
|
||||
buildId: '120.0.6099.109',
|
||||
});
|
||||
|
||||
// Chrome for Testing supports all platforms
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
|
||||
it('should handle ChromeDriver', () => {
|
||||
const result = provider.supports({
|
||||
browser: Browser.CHROMEDRIVER,
|
||||
platform: BrowserPlatform.LINUX,
|
||||
buildId: '120.0.6099.109',
|
||||
});
|
||||
|
||||
// Chrome for Testing supports all browsers
|
||||
assert.strictEqual(result, true);
|
||||
});
|
||||
|
||||
it('should return URL for valid build', () => {
|
||||
const result = provider.getDownloadUrl({
|
||||
browser: Browser.CHROME,
|
||||
platform: BrowserPlatform.LINUX,
|
||||
buildId: '120.0.6099.109',
|
||||
});
|
||||
|
||||
assert(result instanceof URL);
|
||||
assert(result.toString().includes('120.0.6099.109'));
|
||||
});
|
||||
});
|
||||
});
|
||||
62
node_modules/@puppeteer/browsers/src/DefaultProvider.ts
generated
vendored
Normal file
62
node_modules/@puppeteer/browsers/src/DefaultProvider.ts
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {Browser, BrowserPlatform} from './browser-data/browser-data.js';
|
||||
import {
|
||||
downloadUrls,
|
||||
executablePathByBrowser,
|
||||
} from './browser-data/browser-data.js';
|
||||
import type {BrowserProvider, DownloadOptions} from './provider.js';
|
||||
|
||||
/**
|
||||
* Default provider implementation that uses default sources.
|
||||
* This is the standard provider used by Puppeteer.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class DefaultProvider implements BrowserProvider {
|
||||
#baseUrl?: string;
|
||||
|
||||
constructor(baseUrl?: string) {
|
||||
this.#baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
supports(_options: DownloadOptions): boolean {
|
||||
// Default provider supports all browsers
|
||||
return true;
|
||||
}
|
||||
|
||||
getDownloadUrl(options: DownloadOptions): URL {
|
||||
return this.#getDownloadUrl(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.buildId,
|
||||
);
|
||||
}
|
||||
|
||||
#getDownloadUrl(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): URL {
|
||||
return new URL(downloadUrls[browser](platform, buildId, this.#baseUrl));
|
||||
}
|
||||
|
||||
getExecutablePath(options: {
|
||||
browser: Browser;
|
||||
buildId: string;
|
||||
platform: BrowserPlatform;
|
||||
}): string {
|
||||
return executablePathByBrowser[options.browser](
|
||||
options.platform,
|
||||
options.buildId,
|
||||
);
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return 'DefaultProvider';
|
||||
}
|
||||
}
|
||||
302
node_modules/@puppeteer/browsers/src/browser-data/browser-data.ts
generated
vendored
Normal file
302
node_modules/@puppeteer/browsers/src/browser-data/browser-data.ts
generated
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as chromeHeadlessShell from './chrome-headless-shell.js';
|
||||
import * as chrome from './chrome.js';
|
||||
import * as chromedriver from './chromedriver.js';
|
||||
import * as chromium from './chromium.js';
|
||||
import * as firefox from './firefox.js';
|
||||
import {
|
||||
Browser,
|
||||
BrowserPlatform,
|
||||
BrowserTag,
|
||||
ChromeReleaseChannel,
|
||||
type ProfileOptions,
|
||||
} from './types.js';
|
||||
|
||||
export type {ProfileOptions};
|
||||
|
||||
export const downloadUrls = {
|
||||
[Browser.CHROMEDRIVER]: chromedriver.resolveDownloadUrl,
|
||||
[Browser.CHROMEHEADLESSSHELL]: chromeHeadlessShell.resolveDownloadUrl,
|
||||
[Browser.CHROME]: chrome.resolveDownloadUrl,
|
||||
[Browser.CHROMIUM]: chromium.resolveDownloadUrl,
|
||||
[Browser.FIREFOX]: firefox.resolveDownloadUrl,
|
||||
};
|
||||
|
||||
export const downloadPaths = {
|
||||
[Browser.CHROMEDRIVER]: chromedriver.resolveDownloadPath,
|
||||
[Browser.CHROMEHEADLESSSHELL]: chromeHeadlessShell.resolveDownloadPath,
|
||||
[Browser.CHROME]: chrome.resolveDownloadPath,
|
||||
[Browser.CHROMIUM]: chromium.resolveDownloadPath,
|
||||
[Browser.FIREFOX]: firefox.resolveDownloadPath,
|
||||
};
|
||||
|
||||
export const executablePathByBrowser = {
|
||||
[Browser.CHROMEDRIVER]: chromedriver.relativeExecutablePath,
|
||||
[Browser.CHROMEHEADLESSSHELL]: chromeHeadlessShell.relativeExecutablePath,
|
||||
[Browser.CHROME]: chrome.relativeExecutablePath,
|
||||
[Browser.CHROMIUM]: chromium.relativeExecutablePath,
|
||||
[Browser.FIREFOX]: firefox.relativeExecutablePath,
|
||||
};
|
||||
|
||||
export const versionComparators = {
|
||||
[Browser.CHROMEDRIVER]: chromedriver.compareVersions,
|
||||
[Browser.CHROMEHEADLESSSHELL]: chromeHeadlessShell.compareVersions,
|
||||
[Browser.CHROME]: chrome.compareVersions,
|
||||
[Browser.CHROMIUM]: chromium.compareVersions,
|
||||
[Browser.FIREFOX]: firefox.compareVersions,
|
||||
};
|
||||
|
||||
export {Browser, BrowserPlatform, ChromeReleaseChannel};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async function resolveBuildIdForBrowserTag(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
tag: BrowserTag,
|
||||
): Promise<string> {
|
||||
switch (browser) {
|
||||
case Browser.FIREFOX:
|
||||
switch (tag) {
|
||||
case BrowserTag.LATEST:
|
||||
return await firefox.resolveBuildId(firefox.FirefoxChannel.NIGHTLY);
|
||||
case BrowserTag.BETA:
|
||||
return await firefox.resolveBuildId(firefox.FirefoxChannel.BETA);
|
||||
case BrowserTag.NIGHTLY:
|
||||
return await firefox.resolveBuildId(firefox.FirefoxChannel.NIGHTLY);
|
||||
case BrowserTag.DEVEDITION:
|
||||
return await firefox.resolveBuildId(
|
||||
firefox.FirefoxChannel.DEVEDITION,
|
||||
);
|
||||
case BrowserTag.STABLE:
|
||||
return await firefox.resolveBuildId(firefox.FirefoxChannel.STABLE);
|
||||
case BrowserTag.ESR:
|
||||
return await firefox.resolveBuildId(firefox.FirefoxChannel.ESR);
|
||||
case BrowserTag.CANARY:
|
||||
case BrowserTag.DEV:
|
||||
throw new Error(`${tag.toUpperCase()} is not available for Firefox`);
|
||||
}
|
||||
case Browser.CHROME: {
|
||||
switch (tag) {
|
||||
case BrowserTag.LATEST:
|
||||
return await chrome.resolveBuildId(ChromeReleaseChannel.CANARY);
|
||||
case BrowserTag.BETA:
|
||||
return await chrome.resolveBuildId(ChromeReleaseChannel.BETA);
|
||||
case BrowserTag.CANARY:
|
||||
return await chrome.resolveBuildId(ChromeReleaseChannel.CANARY);
|
||||
case BrowserTag.DEV:
|
||||
return await chrome.resolveBuildId(ChromeReleaseChannel.DEV);
|
||||
case BrowserTag.STABLE:
|
||||
return await chrome.resolveBuildId(ChromeReleaseChannel.STABLE);
|
||||
case BrowserTag.NIGHTLY:
|
||||
case BrowserTag.DEVEDITION:
|
||||
case BrowserTag.ESR:
|
||||
throw new Error(`${tag.toUpperCase()} is not available for Chrome`);
|
||||
}
|
||||
}
|
||||
case Browser.CHROMEDRIVER: {
|
||||
switch (tag) {
|
||||
case BrowserTag.LATEST:
|
||||
case BrowserTag.CANARY:
|
||||
return await chromedriver.resolveBuildId(ChromeReleaseChannel.CANARY);
|
||||
case BrowserTag.BETA:
|
||||
return await chromedriver.resolveBuildId(ChromeReleaseChannel.BETA);
|
||||
case BrowserTag.DEV:
|
||||
return await chromedriver.resolveBuildId(ChromeReleaseChannel.DEV);
|
||||
case BrowserTag.STABLE:
|
||||
return await chromedriver.resolveBuildId(ChromeReleaseChannel.STABLE);
|
||||
case BrowserTag.NIGHTLY:
|
||||
case BrowserTag.DEVEDITION:
|
||||
case BrowserTag.ESR:
|
||||
throw new Error(
|
||||
`${tag.toUpperCase()} is not available for ChromeDriver`,
|
||||
);
|
||||
}
|
||||
}
|
||||
case Browser.CHROMEHEADLESSSHELL: {
|
||||
switch (tag) {
|
||||
case BrowserTag.LATEST:
|
||||
case BrowserTag.CANARY:
|
||||
return await chromeHeadlessShell.resolveBuildId(
|
||||
ChromeReleaseChannel.CANARY,
|
||||
);
|
||||
case BrowserTag.BETA:
|
||||
return await chromeHeadlessShell.resolveBuildId(
|
||||
ChromeReleaseChannel.BETA,
|
||||
);
|
||||
case BrowserTag.DEV:
|
||||
return await chromeHeadlessShell.resolveBuildId(
|
||||
ChromeReleaseChannel.DEV,
|
||||
);
|
||||
case BrowserTag.STABLE:
|
||||
return await chromeHeadlessShell.resolveBuildId(
|
||||
ChromeReleaseChannel.STABLE,
|
||||
);
|
||||
case BrowserTag.NIGHTLY:
|
||||
case BrowserTag.DEVEDITION:
|
||||
case BrowserTag.ESR:
|
||||
throw new Error(`${tag} is not available for chrome-headless-shell`);
|
||||
}
|
||||
}
|
||||
case Browser.CHROMIUM:
|
||||
switch (tag) {
|
||||
case BrowserTag.LATEST:
|
||||
return await chromium.resolveBuildId(platform);
|
||||
case BrowserTag.NIGHTLY:
|
||||
case BrowserTag.CANARY:
|
||||
case BrowserTag.DEV:
|
||||
case BrowserTag.DEVEDITION:
|
||||
case BrowserTag.BETA:
|
||||
case BrowserTag.STABLE:
|
||||
case BrowserTag.ESR:
|
||||
throw new Error(
|
||||
`${tag} is not supported for Chromium. Use 'latest' instead.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function resolveBuildId(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
tag: string | BrowserTag,
|
||||
): Promise<string> {
|
||||
const browserTag = tag as BrowserTag;
|
||||
if (Object.values(BrowserTag).includes(browserTag)) {
|
||||
return await resolveBuildIdForBrowserTag(browser, platform, browserTag);
|
||||
}
|
||||
|
||||
switch (browser) {
|
||||
case Browser.FIREFOX:
|
||||
return tag;
|
||||
case Browser.CHROME:
|
||||
const chromeResult = await chrome.resolveBuildId(tag);
|
||||
if (chromeResult) {
|
||||
return chromeResult;
|
||||
}
|
||||
return tag;
|
||||
case Browser.CHROMEDRIVER:
|
||||
const chromeDriverResult = await chromedriver.resolveBuildId(tag);
|
||||
if (chromeDriverResult) {
|
||||
return chromeDriverResult;
|
||||
}
|
||||
return tag;
|
||||
case Browser.CHROMEHEADLESSSHELL:
|
||||
const chromeHeadlessShellResult =
|
||||
await chromeHeadlessShell.resolveBuildId(tag);
|
||||
if (chromeHeadlessShellResult) {
|
||||
return chromeHeadlessShellResult;
|
||||
}
|
||||
return tag;
|
||||
case Browser.CHROMIUM:
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createProfile(
|
||||
browser: Browser,
|
||||
opts: ProfileOptions,
|
||||
): Promise<void> {
|
||||
switch (browser) {
|
||||
case Browser.FIREFOX:
|
||||
return await firefox.createProfile(opts);
|
||||
case Browser.CHROME:
|
||||
case Browser.CHROMIUM:
|
||||
throw new Error(`Profile creation is not support for ${browser} yet`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Get's the first resolved system path
|
||||
*/
|
||||
export function resolveSystemExecutablePath(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
channel: ChromeReleaseChannel,
|
||||
): string {
|
||||
switch (browser) {
|
||||
case Browser.CHROMEDRIVER:
|
||||
case Browser.CHROMEHEADLESSSHELL:
|
||||
case Browser.FIREFOX:
|
||||
case Browser.CHROMIUM:
|
||||
throw new Error(
|
||||
`System browser detection is not supported for ${browser} yet.`,
|
||||
);
|
||||
case Browser.CHROME:
|
||||
return chrome.resolveSystemExecutablePaths(platform, channel)[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expected default user data dir for the given channel. It does not
|
||||
* check if the dir actually exists.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function resolveDefaultUserDataDir(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
channel: ChromeReleaseChannel,
|
||||
): string {
|
||||
switch (browser) {
|
||||
case Browser.CHROMEDRIVER:
|
||||
case Browser.CHROMEHEADLESSSHELL:
|
||||
case Browser.FIREFOX:
|
||||
case Browser.CHROMIUM:
|
||||
throw new Error(
|
||||
`Default user dir detection is not supported for ${browser} yet.`,
|
||||
);
|
||||
case Browser.CHROME:
|
||||
return chrome.resolveDefaultUserDataDir(platform, channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Returns multiple paths where the executable may be located at on the current system
|
||||
* ordered by likelihood (based on heuristics).
|
||||
*/
|
||||
export function resolveSystemExecutablePaths(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
channel: ChromeReleaseChannel,
|
||||
): [string, ...string[]] {
|
||||
switch (browser) {
|
||||
case Browser.CHROMEDRIVER:
|
||||
case Browser.CHROMEHEADLESSSHELL:
|
||||
case Browser.FIREFOX:
|
||||
case Browser.CHROMIUM:
|
||||
throw new Error(
|
||||
`System browser detection is not supported for ${browser} yet.`,
|
||||
);
|
||||
case Browser.CHROME:
|
||||
return chrome.resolveSystemExecutablePaths(platform, channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version comparator for the given browser that can be used to sort
|
||||
* browser versions.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function getVersionComparator(
|
||||
browser: Browser,
|
||||
): (a: string, b: string) => number {
|
||||
return versionComparators[browser];
|
||||
}
|
||||
71
node_modules/@puppeteer/browsers/src/browser-data/chrome-headless-shell.ts
generated
vendored
Normal file
71
node_modules/@puppeteer/browsers/src/browser-data/chrome-headless-shell.ts
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import path from 'node:path';
|
||||
|
||||
import {BrowserPlatform} from './types.js';
|
||||
|
||||
function folder(platform: BrowserPlatform): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return 'linux64';
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
return 'mac-arm64';
|
||||
case BrowserPlatform.MAC:
|
||||
return 'mac-x64';
|
||||
case BrowserPlatform.WIN32:
|
||||
return 'win32';
|
||||
case BrowserPlatform.WIN64:
|
||||
return 'win64';
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDownloadUrl(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public',
|
||||
): string {
|
||||
return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`;
|
||||
}
|
||||
|
||||
export function resolveDownloadPath(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): string[] {
|
||||
return [
|
||||
buildId,
|
||||
folder(platform),
|
||||
`chrome-headless-shell-${folder(platform)}.zip`,
|
||||
];
|
||||
}
|
||||
|
||||
export function relativeExecutablePath(
|
||||
platform: BrowserPlatform,
|
||||
_buildId: string,
|
||||
): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.MAC:
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
return path.join(
|
||||
'chrome-headless-shell-' + folder(platform),
|
||||
'chrome-headless-shell',
|
||||
);
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return path.join(
|
||||
'chrome-headless-shell-linux64',
|
||||
'chrome-headless-shell',
|
||||
);
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return path.join(
|
||||
'chrome-headless-shell-' + folder(platform),
|
||||
'chrome-headless-shell.exe',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {resolveBuildId, compareVersions} from './chrome.js';
|
||||
422
node_modules/@puppeteer/browsers/src/browser-data/chrome.ts
generated
vendored
Normal file
422
node_modules/@puppeteer/browsers/src/browser-data/chrome.ts
generated
vendored
Normal file
@@ -0,0 +1,422 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {execSync} from 'node:child_process';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import semver from 'semver';
|
||||
|
||||
import {getJSON} from '../httpUtil.js';
|
||||
|
||||
import {BrowserPlatform, ChromeReleaseChannel} from './types.js';
|
||||
|
||||
function folder(platform: BrowserPlatform): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return 'linux64';
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
return 'mac-arm64';
|
||||
case BrowserPlatform.MAC:
|
||||
return 'mac-x64';
|
||||
case BrowserPlatform.WIN32:
|
||||
return 'win32';
|
||||
case BrowserPlatform.WIN64:
|
||||
return 'win64';
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDownloadUrl(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public',
|
||||
): string {
|
||||
return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`;
|
||||
}
|
||||
|
||||
export function resolveDownloadPath(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): string[] {
|
||||
return [buildId, folder(platform), `chrome-${folder(platform)}.zip`];
|
||||
}
|
||||
|
||||
export function relativeExecutablePath(
|
||||
platform: BrowserPlatform,
|
||||
_buildId: string,
|
||||
): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.MAC:
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
return path.join(
|
||||
'chrome-' + folder(platform),
|
||||
'Google Chrome for Testing.app',
|
||||
'Contents',
|
||||
'MacOS',
|
||||
'Google Chrome for Testing',
|
||||
);
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return path.join('chrome-linux64', 'chrome');
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return path.join('chrome-' + folder(platform), 'chrome.exe');
|
||||
}
|
||||
}
|
||||
|
||||
let baseVersionUrl = 'https://googlechromelabs.github.io/chrome-for-testing';
|
||||
|
||||
export function changeBaseVersionUrlForTesting(url: string): void {
|
||||
baseVersionUrl = url;
|
||||
}
|
||||
export function resetBaseVersionUrlForTesting(): void {
|
||||
baseVersionUrl = 'https://googlechromelabs.github.io/chrome-for-testing';
|
||||
}
|
||||
|
||||
export async function getLastKnownGoodReleaseForChannel(
|
||||
channel: ChromeReleaseChannel,
|
||||
): Promise<{version: string; revision: string}> {
|
||||
const data = (await getJSON(
|
||||
new URL(`${baseVersionUrl}/last-known-good-versions.json`),
|
||||
)) as {
|
||||
channels: Record<string, {version: string}>;
|
||||
};
|
||||
|
||||
for (const channel of Object.keys(data.channels)) {
|
||||
data.channels[channel.toLowerCase()] = data.channels[channel]!;
|
||||
delete data.channels[channel];
|
||||
}
|
||||
|
||||
return (
|
||||
data as {
|
||||
channels: Record<
|
||||
ChromeReleaseChannel,
|
||||
{version: string; revision: string}
|
||||
>;
|
||||
}
|
||||
).channels[channel];
|
||||
}
|
||||
|
||||
export async function getLastKnownGoodReleaseForMilestone(
|
||||
milestone: string,
|
||||
): Promise<{version: string; revision: string} | undefined> {
|
||||
const data = (await getJSON(
|
||||
new URL(`${baseVersionUrl}/latest-versions-per-milestone.json`),
|
||||
)) as {
|
||||
milestones: Record<string, {version: string; revision: string}>;
|
||||
};
|
||||
return data.milestones[milestone] as
|
||||
| {version: string; revision: string}
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export async function getLastKnownGoodReleaseForBuild(
|
||||
/**
|
||||
* @example `112.0.23`,
|
||||
*/
|
||||
buildPrefix: string,
|
||||
): Promise<{version: string; revision: string} | undefined> {
|
||||
const data = (await getJSON(
|
||||
new URL(`${baseVersionUrl}/latest-patch-versions-per-build.json`),
|
||||
)) as {
|
||||
builds: Record<string, {version: string; revision: string}>;
|
||||
};
|
||||
return data.builds[buildPrefix] as
|
||||
| {version: string; revision: string}
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export async function resolveBuildId(
|
||||
channel: ChromeReleaseChannel,
|
||||
): Promise<string>;
|
||||
export async function resolveBuildId(
|
||||
channel: string,
|
||||
): Promise<string | undefined>;
|
||||
export async function resolveBuildId(
|
||||
channel: ChromeReleaseChannel | string,
|
||||
): Promise<string | undefined> {
|
||||
if (
|
||||
Object.values(ChromeReleaseChannel).includes(
|
||||
channel as ChromeReleaseChannel,
|
||||
)
|
||||
) {
|
||||
return (
|
||||
await getLastKnownGoodReleaseForChannel(channel as ChromeReleaseChannel)
|
||||
).version;
|
||||
}
|
||||
if (channel.match(/^\d+$/)) {
|
||||
// Potentially a milestone.
|
||||
return (await getLastKnownGoodReleaseForMilestone(channel))?.version;
|
||||
}
|
||||
if (channel.match(/^\d+\.\d+\.\d+$/)) {
|
||||
// Potentially a build prefix without the patch version.
|
||||
return (await getLastKnownGoodReleaseForBuild(channel))?.version;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const WINDOWS_ENV_PARAM_NAMES = [
|
||||
'PROGRAMFILES',
|
||||
'ProgramW6432',
|
||||
'ProgramFiles(x86)',
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/mini_installer/README.md
|
||||
'LOCALAPPDATA',
|
||||
];
|
||||
|
||||
function getChromeWindowsLocation(
|
||||
channel: ChromeReleaseChannel,
|
||||
locationsPrefixes: Set<string>,
|
||||
): [string, ...string[]] {
|
||||
if (locationsPrefixes.size === 0) {
|
||||
throw new Error('Non of the common Windows Env variables were set');
|
||||
}
|
||||
|
||||
let suffix: string;
|
||||
switch (channel) {
|
||||
case ChromeReleaseChannel.STABLE:
|
||||
suffix = 'Google\\Chrome\\Application\\chrome.exe';
|
||||
break;
|
||||
case ChromeReleaseChannel.BETA:
|
||||
suffix = 'Google\\Chrome Beta\\Application\\chrome.exe';
|
||||
break;
|
||||
case ChromeReleaseChannel.CANARY:
|
||||
suffix = 'Google\\Chrome SxS\\Application\\chrome.exe';
|
||||
break;
|
||||
case ChromeReleaseChannel.DEV:
|
||||
suffix = 'Google\\Chrome Dev\\Application\\chrome.exe';
|
||||
break;
|
||||
}
|
||||
|
||||
return [...locationsPrefixes.values()].map(l => {
|
||||
return path.win32.join(l, suffix);
|
||||
}) as [string, ...string[]];
|
||||
}
|
||||
|
||||
function getWslVariable(variable: string): string | undefined {
|
||||
try {
|
||||
// The Windows env for the paths are not passed down
|
||||
// to WSL, so we evoke `cmd.exe` which is usually on the PATH
|
||||
// from which the env can be access with all uppercase names.
|
||||
// The return value is a Windows Path - `C:\Program Files`.
|
||||
|
||||
const result = execSync(
|
||||
`cmd.exe /c echo %${variable.toLocaleUpperCase()}%`,
|
||||
{
|
||||
// We need to ignore the stderr as cmd.exe
|
||||
// prints a message about wrong UNC path not supported.
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
encoding: 'utf-8',
|
||||
},
|
||||
).trim();
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
} catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
function getWslLocation(channel: ChromeReleaseChannel): [string, ...string[]] {
|
||||
const wslVersion = execSync('wslinfo --version', {
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
if (!wslVersion) {
|
||||
throw new Error('Not in WSL or unsupported version of WSL.');
|
||||
}
|
||||
const wslPrefixes = new Set<string>();
|
||||
for (const name of WINDOWS_ENV_PARAM_NAMES) {
|
||||
const wslPrefix = getWslVariable(name);
|
||||
if (wslPrefix) {
|
||||
wslPrefixes.add(wslPrefix);
|
||||
}
|
||||
}
|
||||
const windowsPath = getChromeWindowsLocation(channel, wslPrefixes);
|
||||
|
||||
return windowsPath.map(path => {
|
||||
// The above command returned the Windows paths `C:\Program Files\...\chrome.exe`
|
||||
// Use the `wslpath` utility tool to transform into the mounted disk
|
||||
return execSync(`wslpath "${path}"`).toString().trim();
|
||||
}) as [string, ...string[]];
|
||||
}
|
||||
|
||||
function getChromeLinuxOrWslLocation(
|
||||
channel: ChromeReleaseChannel,
|
||||
): [string, ...string[]] {
|
||||
const locations: string[] = [];
|
||||
|
||||
try {
|
||||
const wslPath = getWslLocation(channel);
|
||||
if (wslPath) {
|
||||
locations.push(...wslPath);
|
||||
}
|
||||
} catch {
|
||||
// Ignore WSL errors
|
||||
}
|
||||
|
||||
switch (channel) {
|
||||
case ChromeReleaseChannel.STABLE:
|
||||
locations.push('/opt/google/chrome/chrome');
|
||||
break;
|
||||
case ChromeReleaseChannel.BETA:
|
||||
locations.push('/opt/google/chrome-beta/chrome');
|
||||
break;
|
||||
case ChromeReleaseChannel.CANARY:
|
||||
locations.push('/opt/google/chrome-canary/chrome');
|
||||
break;
|
||||
case ChromeReleaseChannel.DEV:
|
||||
locations.push('/opt/google/chrome-unstable/chrome');
|
||||
break;
|
||||
}
|
||||
|
||||
return locations as [string, ...string[]];
|
||||
}
|
||||
|
||||
export function resolveSystemExecutablePaths(
|
||||
platform: BrowserPlatform,
|
||||
channel: ChromeReleaseChannel,
|
||||
): [string, ...string[]] {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.WIN64:
|
||||
case BrowserPlatform.WIN32:
|
||||
const prefixLocation = new Set<string>(
|
||||
WINDOWS_ENV_PARAM_NAMES.map(name => {
|
||||
return process.env[name];
|
||||
}).filter((l): l is string => {
|
||||
return !!l;
|
||||
}),
|
||||
);
|
||||
// Fallbacks in case env vars are misconfigured.
|
||||
prefixLocation.add('C:\\Program Files');
|
||||
prefixLocation.add('C:\\Program Files (x86)');
|
||||
prefixLocation.add('D:\\Program Files');
|
||||
prefixLocation.add('D:\\Program Files (x86)');
|
||||
return getChromeWindowsLocation(channel, prefixLocation);
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
case BrowserPlatform.MAC:
|
||||
switch (channel) {
|
||||
case ChromeReleaseChannel.STABLE:
|
||||
return [
|
||||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
];
|
||||
case ChromeReleaseChannel.BETA:
|
||||
return [
|
||||
'/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
|
||||
];
|
||||
case ChromeReleaseChannel.CANARY:
|
||||
return [
|
||||
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
||||
];
|
||||
case ChromeReleaseChannel.DEV:
|
||||
return [
|
||||
'/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',
|
||||
];
|
||||
}
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return getChromeLinuxOrWslLocation(channel);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDefaultUserDataDir(
|
||||
platform: BrowserPlatform,
|
||||
channel: ChromeReleaseChannel,
|
||||
): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.WIN64:
|
||||
case BrowserPlatform.WIN32:
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/common/chrome_paths_win.cc;l=42;drc=4c86c7940a47c36b8bf52c134483ef2da86caa62
|
||||
switch (channel) {
|
||||
case ChromeReleaseChannel.STABLE:
|
||||
return path.join(
|
||||
getLocalAppDataWin(),
|
||||
'Google',
|
||||
'Chrome',
|
||||
'User Data',
|
||||
);
|
||||
case ChromeReleaseChannel.BETA:
|
||||
return path.join(
|
||||
getLocalAppDataWin(),
|
||||
'Google',
|
||||
'Chrome Beta',
|
||||
'User Data',
|
||||
);
|
||||
case ChromeReleaseChannel.CANARY:
|
||||
return path.join(
|
||||
getLocalAppDataWin(),
|
||||
'Google',
|
||||
'Chrome SxS',
|
||||
'User Data',
|
||||
);
|
||||
case ChromeReleaseChannel.DEV:
|
||||
return path.join(
|
||||
getLocalAppDataWin(),
|
||||
'Google',
|
||||
'Chrome Dev',
|
||||
'User Data',
|
||||
);
|
||||
}
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
case BrowserPlatform.MAC:
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/common/chrome_paths_mac.mm;l=86;drc=4c86c7940a47c36b8bf52c134483ef2da86caa62
|
||||
switch (channel) {
|
||||
case ChromeReleaseChannel.STABLE:
|
||||
return path.join(getBaseUserDataDirPathMac(), 'Chrome');
|
||||
case ChromeReleaseChannel.BETA:
|
||||
return path.join(getBaseUserDataDirPathMac(), 'Chrome Beta');
|
||||
case ChromeReleaseChannel.DEV:
|
||||
return path.join(getBaseUserDataDirPathMac(), 'Chrome Dev');
|
||||
case ChromeReleaseChannel.CANARY:
|
||||
return path.join(getBaseUserDataDirPathMac(), 'Chrome Canary');
|
||||
}
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/common/chrome_paths_linux.cc;l=80;drc=4c86c7940a47c36b8bf52c134483ef2da86caa62
|
||||
switch (channel) {
|
||||
case ChromeReleaseChannel.STABLE:
|
||||
return path.join(getConfigHomeLinux(), 'google-chrome');
|
||||
case ChromeReleaseChannel.BETA:
|
||||
return path.join(getConfigHomeLinux(), 'google-chrome-beta');
|
||||
case ChromeReleaseChannel.CANARY:
|
||||
return path.join(getConfigHomeLinux(), 'google-chrome-canary');
|
||||
case ChromeReleaseChannel.DEV:
|
||||
return path.join(getConfigHomeLinux(), 'google-chrome-unstable');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLocalAppDataWin() {
|
||||
return (
|
||||
process.env['LOCALAPPDATA'] || path.join(os.homedir(), 'AppData', 'Local')
|
||||
);
|
||||
}
|
||||
|
||||
function getConfigHomeLinux() {
|
||||
return (
|
||||
process.env['CHROME_CONFIG_HOME'] ||
|
||||
process.env['XDG_CONFIG_HOME'] ||
|
||||
path.join(os.homedir(), '.config')
|
||||
);
|
||||
}
|
||||
|
||||
function getBaseUserDataDirPathMac() {
|
||||
return path.join(os.homedir(), 'Library', 'Application Support', 'Google');
|
||||
}
|
||||
|
||||
export function compareVersions(a: string, b: string): number {
|
||||
if (!semver.valid(a)) {
|
||||
throw new Error(`Version ${a} is not a valid semver version`);
|
||||
}
|
||||
if (!semver.valid(b)) {
|
||||
throw new Error(`Version ${b} is not a valid semver version`);
|
||||
}
|
||||
if (semver.gt(a, b)) {
|
||||
return 1;
|
||||
} else if (semver.lt(a, b)) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
58
node_modules/@puppeteer/browsers/src/browser-data/chromedriver.ts
generated
vendored
Normal file
58
node_modules/@puppeteer/browsers/src/browser-data/chromedriver.ts
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import path from 'node:path';
|
||||
|
||||
import {BrowserPlatform} from './types.js';
|
||||
|
||||
function folder(platform: BrowserPlatform): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return 'linux64';
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
return 'mac-arm64';
|
||||
case BrowserPlatform.MAC:
|
||||
return 'mac-x64';
|
||||
case BrowserPlatform.WIN32:
|
||||
return 'win32';
|
||||
case BrowserPlatform.WIN64:
|
||||
return 'win64';
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDownloadUrl(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
baseUrl = 'https://storage.googleapis.com/chrome-for-testing-public',
|
||||
): string {
|
||||
return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`;
|
||||
}
|
||||
|
||||
export function resolveDownloadPath(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): string[] {
|
||||
return [buildId, folder(platform), `chromedriver-${folder(platform)}.zip`];
|
||||
}
|
||||
|
||||
export function relativeExecutablePath(
|
||||
platform: BrowserPlatform,
|
||||
_buildId: string,
|
||||
): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.MAC:
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
return path.join('chromedriver-' + folder(platform), 'chromedriver');
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return path.join('chromedriver-linux64', 'chromedriver');
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return path.join('chromedriver-' + folder(platform), 'chromedriver.exe');
|
||||
}
|
||||
}
|
||||
|
||||
export {resolveBuildId, compareVersions} from './chrome.js';
|
||||
95
node_modules/@puppeteer/browsers/src/browser-data/chromium.ts
generated
vendored
Normal file
95
node_modules/@puppeteer/browsers/src/browser-data/chromium.ts
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
|
||||
import {getText} from '../httpUtil.js';
|
||||
|
||||
import {BrowserPlatform} from './types.js';
|
||||
|
||||
function archive(platform: BrowserPlatform, buildId: string): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return 'chrome-linux';
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
case BrowserPlatform.MAC:
|
||||
return 'chrome-mac';
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
// Windows archive name changed at r591479.
|
||||
return parseInt(buildId, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
|
||||
}
|
||||
}
|
||||
|
||||
function folder(platform: BrowserPlatform): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return 'Linux_x64';
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
return 'Mac_Arm';
|
||||
case BrowserPlatform.MAC:
|
||||
return 'Mac';
|
||||
case BrowserPlatform.WIN32:
|
||||
return 'Win';
|
||||
case BrowserPlatform.WIN64:
|
||||
return 'Win_x64';
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDownloadUrl(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
baseUrl = 'https://storage.googleapis.com/chromium-browser-snapshots',
|
||||
): string {
|
||||
return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`;
|
||||
}
|
||||
|
||||
export function resolveDownloadPath(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): string[] {
|
||||
return [folder(platform), buildId, `${archive(platform, buildId)}.zip`];
|
||||
}
|
||||
|
||||
export function relativeExecutablePath(
|
||||
platform: BrowserPlatform,
|
||||
_buildId: string,
|
||||
): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.MAC:
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
return path.join(
|
||||
'chrome-mac',
|
||||
'Chromium.app',
|
||||
'Contents',
|
||||
'MacOS',
|
||||
'Chromium',
|
||||
);
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return path.join('chrome-linux', 'chrome');
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return path.join('chrome-win', 'chrome.exe');
|
||||
}
|
||||
}
|
||||
export async function resolveBuildId(
|
||||
platform: BrowserPlatform,
|
||||
): Promise<string> {
|
||||
return await getText(
|
||||
new URL(
|
||||
`https://storage.googleapis.com/chromium-browser-snapshots/${folder(
|
||||
platform,
|
||||
)}/LAST_CHANGE`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function compareVersions(a: string, b: string): number {
|
||||
return Number(a) - Number(b);
|
||||
}
|
||||
469
node_modules/@puppeteer/browsers/src/browser-data/firefox.ts
generated
vendored
Normal file
469
node_modules/@puppeteer/browsers/src/browser-data/firefox.ts
generated
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import {getJSON} from '../httpUtil.js';
|
||||
|
||||
import {BrowserPlatform, type ProfileOptions} from './types.js';
|
||||
|
||||
function getFormat(buildId: string): string {
|
||||
const majorVersion = Number(buildId.split('.').shift()!);
|
||||
return majorVersion >= 135 ? 'xz' : 'bz2';
|
||||
}
|
||||
|
||||
function archiveNightly(platform: BrowserPlatform, buildId: string): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.LINUX:
|
||||
return `firefox-${buildId}.en-US.linux-x86_64.tar.${getFormat(buildId)}`;
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
return `firefox-${buildId}.en-US.linux-aarch64.tar.${getFormat(buildId)}`;
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
case BrowserPlatform.MAC:
|
||||
return `firefox-${buildId}.en-US.mac.dmg`;
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return `firefox-${buildId}.en-US.${platform}.zip`;
|
||||
}
|
||||
}
|
||||
|
||||
function archive(platform: BrowserPlatform, buildId: string): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return `firefox-${buildId}.tar.${getFormat(buildId)}`;
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
case BrowserPlatform.MAC:
|
||||
return `Firefox ${buildId}.dmg`;
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return `Firefox Setup ${buildId}.exe`;
|
||||
}
|
||||
}
|
||||
|
||||
function platformName(platform: BrowserPlatform): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.LINUX:
|
||||
return `linux-x86_64`;
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
return `linux-aarch64`;
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
case BrowserPlatform.MAC:
|
||||
return `mac`;
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
|
||||
function parseBuildId(buildId: string): [FirefoxChannel, string] {
|
||||
for (const value of Object.values(FirefoxChannel)) {
|
||||
if (buildId.startsWith(value + '_')) {
|
||||
buildId = buildId.substring(value.length + 1);
|
||||
return [value, buildId];
|
||||
}
|
||||
}
|
||||
// Older versions do not have channel as the prefix.«
|
||||
return [FirefoxChannel.NIGHTLY, buildId];
|
||||
}
|
||||
|
||||
export function resolveDownloadUrl(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
baseUrl?: string,
|
||||
): string {
|
||||
const [channel] = parseBuildId(buildId);
|
||||
switch (channel) {
|
||||
case FirefoxChannel.NIGHTLY:
|
||||
baseUrl ??=
|
||||
'https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central';
|
||||
break;
|
||||
case FirefoxChannel.DEVEDITION:
|
||||
baseUrl ??= 'https://archive.mozilla.org/pub/devedition/releases';
|
||||
break;
|
||||
case FirefoxChannel.BETA:
|
||||
case FirefoxChannel.STABLE:
|
||||
case FirefoxChannel.ESR:
|
||||
baseUrl ??= 'https://archive.mozilla.org/pub/firefox/releases';
|
||||
break;
|
||||
}
|
||||
return `${baseUrl}/${resolveDownloadPath(platform, buildId).join('/')}`;
|
||||
}
|
||||
|
||||
export function resolveDownloadPath(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): string[] {
|
||||
const [channel, resolvedBuildId] = parseBuildId(buildId);
|
||||
switch (channel) {
|
||||
case FirefoxChannel.NIGHTLY:
|
||||
return [archiveNightly(platform, resolvedBuildId)];
|
||||
case FirefoxChannel.DEVEDITION:
|
||||
case FirefoxChannel.BETA:
|
||||
case FirefoxChannel.STABLE:
|
||||
case FirefoxChannel.ESR:
|
||||
return [
|
||||
resolvedBuildId,
|
||||
platformName(platform),
|
||||
'en-US',
|
||||
archive(platform, resolvedBuildId),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export function relativeExecutablePath(
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
): string {
|
||||
const [channel] = parseBuildId(buildId);
|
||||
switch (channel) {
|
||||
case FirefoxChannel.NIGHTLY:
|
||||
switch (platform) {
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
case BrowserPlatform.MAC:
|
||||
return path.join(
|
||||
'Firefox Nightly.app',
|
||||
'Contents',
|
||||
'MacOS',
|
||||
'firefox',
|
||||
);
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return path.join('firefox', 'firefox');
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return path.join('firefox', 'firefox.exe');
|
||||
}
|
||||
case FirefoxChannel.BETA:
|
||||
case FirefoxChannel.DEVEDITION:
|
||||
case FirefoxChannel.ESR:
|
||||
case FirefoxChannel.STABLE:
|
||||
switch (platform) {
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
case BrowserPlatform.MAC:
|
||||
return path.join('Firefox.app', 'Contents', 'MacOS', 'firefox');
|
||||
case BrowserPlatform.LINUX_ARM:
|
||||
case BrowserPlatform.LINUX:
|
||||
return path.join('firefox', 'firefox');
|
||||
case BrowserPlatform.WIN32:
|
||||
case BrowserPlatform.WIN64:
|
||||
return path.join('core', 'firefox.exe');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export enum FirefoxChannel {
|
||||
STABLE = 'stable',
|
||||
ESR = 'esr',
|
||||
DEVEDITION = 'devedition',
|
||||
BETA = 'beta',
|
||||
NIGHTLY = 'nightly',
|
||||
}
|
||||
|
||||
let baseVersionUrl = 'https://product-details.mozilla.org/1.0';
|
||||
|
||||
export function changeBaseVersionUrlForTesting(url: string): void {
|
||||
baseVersionUrl = url;
|
||||
}
|
||||
|
||||
export function resetBaseVersionUrlForTesting(): void {
|
||||
baseVersionUrl = 'https://product-details.mozilla.org/1.0';
|
||||
}
|
||||
|
||||
export async function resolveBuildId(
|
||||
channel: FirefoxChannel = FirefoxChannel.NIGHTLY,
|
||||
): Promise<string> {
|
||||
const channelToVersionKey = {
|
||||
[FirefoxChannel.ESR]: 'FIREFOX_ESR',
|
||||
[FirefoxChannel.STABLE]: 'LATEST_FIREFOX_VERSION',
|
||||
[FirefoxChannel.DEVEDITION]: 'FIREFOX_DEVEDITION',
|
||||
[FirefoxChannel.BETA]: 'FIREFOX_DEVEDITION',
|
||||
[FirefoxChannel.NIGHTLY]: 'FIREFOX_NIGHTLY',
|
||||
};
|
||||
const versions = (await getJSON(
|
||||
new URL(`${baseVersionUrl}/firefox_versions.json`),
|
||||
)) as Record<string, string>;
|
||||
const version = versions[channelToVersionKey[channel]];
|
||||
if (!version) {
|
||||
throw new Error(`Channel ${channel} is not found.`);
|
||||
}
|
||||
return channel + '_' + version;
|
||||
}
|
||||
|
||||
export async function createProfile(options: ProfileOptions): Promise<void> {
|
||||
if (!fs.existsSync(options.path)) {
|
||||
await fs.promises.mkdir(options.path, {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
await syncPreferences({
|
||||
preferences: {
|
||||
...defaultProfilePreferences(options.preferences),
|
||||
...options.preferences,
|
||||
},
|
||||
path: options.path,
|
||||
});
|
||||
}
|
||||
|
||||
function defaultProfilePreferences(
|
||||
extraPrefs: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const server = 'dummy.test';
|
||||
|
||||
const defaultPrefs = {
|
||||
// Make sure Shield doesn't hit the network.
|
||||
'app.normandy.api_url': '',
|
||||
// Disable Firefox old build background check
|
||||
'app.update.checkInstallTime': false,
|
||||
// Disable automatically upgrading Firefox
|
||||
'app.update.disabledForTesting': true,
|
||||
|
||||
// Increase the APZ content response timeout to 1 minute
|
||||
'apz.content_response_timeout': 60000,
|
||||
|
||||
// Prevent various error message on the console
|
||||
// jest-puppeteer asserts that no error message is emitted by the console
|
||||
'browser.contentblocking.features.standard':
|
||||
'-tp,tpPrivate,cookieBehavior0,-cryptoTP,-fp',
|
||||
|
||||
// Enable the dump function: which sends messages to the system
|
||||
// console
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1543115
|
||||
'browser.dom.window.dump.enabled': true,
|
||||
// Disable topstories
|
||||
'browser.newtabpage.activity-stream.feeds.system.topstories': false,
|
||||
// Always display a blank page
|
||||
'browser.newtabpage.enabled': false,
|
||||
// Background thumbnails in particular cause grief: and disabling
|
||||
// thumbnails in general cannot hurt
|
||||
'browser.pagethumbnails.capturing_disabled': true,
|
||||
|
||||
// Disable safebrowsing components.
|
||||
'browser.safebrowsing.blockedURIs.enabled': false,
|
||||
'browser.safebrowsing.downloads.enabled': false,
|
||||
'browser.safebrowsing.malware.enabled': false,
|
||||
'browser.safebrowsing.phishing.enabled': false,
|
||||
|
||||
// Disable updates to search engines.
|
||||
'browser.search.update': false,
|
||||
// Do not restore the last open set of tabs if the browser has crashed
|
||||
'browser.sessionstore.resume_from_crash': false,
|
||||
// Skip check for default browser on startup
|
||||
'browser.shell.checkDefaultBrowser': false,
|
||||
|
||||
// Disable newtabpage
|
||||
'browser.startup.homepage': 'about:blank',
|
||||
// Do not redirect user when a milstone upgrade of Firefox is detected
|
||||
'browser.startup.homepage_override.mstone': 'ignore',
|
||||
// Start with a blank page about:blank
|
||||
'browser.startup.page': 0,
|
||||
|
||||
// Do not allow background tabs to be zombified on Android: otherwise for
|
||||
// tests that open additional tabs: the test harness tab itself might get
|
||||
// unloaded
|
||||
'browser.tabs.disableBackgroundZombification': false,
|
||||
// Do not warn when closing all other open tabs
|
||||
'browser.tabs.warnOnCloseOtherTabs': false,
|
||||
// Do not warn when multiple tabs will be opened
|
||||
'browser.tabs.warnOnOpen': false,
|
||||
|
||||
// Do not automatically offer translations, as tests do not expect this.
|
||||
'browser.translations.automaticallyPopup': false,
|
||||
|
||||
// Disable the UI tour.
|
||||
'browser.uitour.enabled': false,
|
||||
// Turn off search suggestions in the location bar so as not to trigger
|
||||
// network connections.
|
||||
'browser.urlbar.suggest.searches': false,
|
||||
// Disable first run splash page on Windows 10
|
||||
'browser.usedOnWindows10.introURL': '',
|
||||
// Do not warn on quitting Firefox
|
||||
'browser.warnOnQuit': false,
|
||||
|
||||
// Defensively disable data reporting systems
|
||||
'datareporting.healthreport.documentServerURI': `http://${server}/dummy/healthreport/`,
|
||||
'datareporting.healthreport.logging.consoleEnabled': false,
|
||||
'datareporting.healthreport.service.enabled': false,
|
||||
'datareporting.healthreport.service.firstRun': false,
|
||||
'datareporting.healthreport.uploadEnabled': false,
|
||||
|
||||
// Do not show datareporting policy notifications which can interfere with tests
|
||||
'datareporting.policy.dataSubmissionEnabled': false,
|
||||
'datareporting.policy.dataSubmissionPolicyBypassNotification': true,
|
||||
|
||||
// DevTools JSONViewer sometimes fails to load dependencies with its require.js.
|
||||
// This doesn't affect Puppeteer but spams console (Bug 1424372)
|
||||
'devtools.jsonview.enabled': false,
|
||||
|
||||
// Disable popup-blocker
|
||||
'dom.disable_open_during_load': false,
|
||||
|
||||
// Enable the support for File object creation in the content process
|
||||
// Required for |Page.setFileInputFiles| protocol method.
|
||||
'dom.file.createInChild': true,
|
||||
|
||||
// Disable the ProcessHangMonitor
|
||||
'dom.ipc.reportProcessHangs': false,
|
||||
|
||||
// Disable slow script dialogues
|
||||
'dom.max_chrome_script_run_time': 0,
|
||||
'dom.max_script_run_time': 0,
|
||||
|
||||
// Only load extensions from the application and user profile
|
||||
// AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
|
||||
'extensions.autoDisableScopes': 0,
|
||||
'extensions.enabledScopes': 5,
|
||||
|
||||
// Disable metadata caching for installed add-ons by default
|
||||
'extensions.getAddons.cache.enabled': false,
|
||||
|
||||
// Disable installing any distribution extensions or add-ons.
|
||||
'extensions.installDistroAddons': false,
|
||||
|
||||
// Turn off extension updates so they do not bother tests
|
||||
'extensions.update.enabled': false,
|
||||
|
||||
// Turn off extension updates so they do not bother tests
|
||||
'extensions.update.notifyUser': false,
|
||||
|
||||
// Make sure opening about:addons will not hit the network
|
||||
'extensions.webservice.discoverURL': `http://${server}/dummy/discoveryURL`,
|
||||
|
||||
// Allow the application to have focus even it runs in the background
|
||||
'focusmanager.testmode': true,
|
||||
|
||||
// Disable useragent updates
|
||||
'general.useragent.updates.enabled': false,
|
||||
|
||||
// Always use network provider for geolocation tests so we bypass the
|
||||
// macOS dialog raised by the corelocation provider
|
||||
'geo.provider.testing': true,
|
||||
|
||||
// Do not scan Wifi
|
||||
'geo.wifi.scan': false,
|
||||
|
||||
// No hang monitor
|
||||
'hangmonitor.timeout': 0,
|
||||
|
||||
// Show chrome errors and warnings in the error console
|
||||
'javascript.options.showInConsole': true,
|
||||
|
||||
// Disable download and usage of OpenH264: and Widevine plugins
|
||||
'media.gmp-manager.updateEnabled': false,
|
||||
|
||||
// Disable the GFX sanity window
|
||||
'media.sanity-test.disabled': true,
|
||||
|
||||
// Disable experimental feature that is only available in Nightly
|
||||
'network.cookie.sameSite.laxByDefault': false,
|
||||
|
||||
// Do not prompt for temporary redirects
|
||||
'network.http.prompt-temp-redirect': false,
|
||||
|
||||
// Disable speculative connections so they are not reported as leaking
|
||||
// when they are hanging around
|
||||
'network.http.speculative-parallel-limit': 0,
|
||||
|
||||
// Do not automatically switch between offline and online
|
||||
'network.manage-offline-status': false,
|
||||
|
||||
// Make sure SNTP requests do not hit the network
|
||||
'network.sntp.pools': server,
|
||||
|
||||
// Disable Flash.
|
||||
'plugin.state.flash': 0,
|
||||
|
||||
'privacy.trackingprotection.enabled': false,
|
||||
|
||||
// Can be removed once Firefox 89 is no longer supported
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1710839
|
||||
'remote.enabled': true,
|
||||
|
||||
// Until Bug 1999693 is resolved, this preference needs to be set to allow
|
||||
// Webdriver BiDi to automatically dismiss file pickers.
|
||||
'remote.bidi.dismiss_file_pickers.enabled': true,
|
||||
|
||||
// Disabled screenshots component
|
||||
'screenshots.browser.component.enabled': false,
|
||||
|
||||
// Don't do network connections for mitm priming
|
||||
'security.certerrors.mitm.priming.enabled': false,
|
||||
|
||||
// Local documents have access to all other local documents,
|
||||
// including directory listings
|
||||
'security.fileuri.strict_origin_policy': false,
|
||||
|
||||
// Do not wait for the notification button security delay
|
||||
'security.notification_enable_delay': 0,
|
||||
|
||||
// Ensure blocklist updates do not hit the network
|
||||
'services.settings.server': `http://${server}/dummy/blocklist/`,
|
||||
|
||||
// Do not automatically fill sign-in forms with known usernames and
|
||||
// passwords
|
||||
'signon.autofillForms': false,
|
||||
|
||||
// Disable password capture, so that tests that include forms are not
|
||||
// influenced by the presence of the persistent doorhanger notification
|
||||
'signon.rememberSignons': false,
|
||||
|
||||
// Disable first-run welcome page
|
||||
'startup.homepage_welcome_url': 'about:blank',
|
||||
|
||||
// Disable first-run welcome page
|
||||
'startup.homepage_welcome_url.additional': '',
|
||||
|
||||
// Disable browser animations (tabs, fullscreen, sliding alerts)
|
||||
'toolkit.cosmeticAnimations.enabled': false,
|
||||
|
||||
// Prevent starting into safe mode after application crashes
|
||||
'toolkit.startup.max_resumed_crashes': -1,
|
||||
};
|
||||
|
||||
return Object.assign(defaultPrefs, extraPrefs);
|
||||
}
|
||||
|
||||
async function backupFile(input: string): Promise<void> {
|
||||
if (!fs.existsSync(input)) {
|
||||
return;
|
||||
}
|
||||
await fs.promises.copyFile(input, input + '.puppeteer');
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the user.js file with custom preferences as needed to allow
|
||||
* Firefox's support to properly function. These preferences will be
|
||||
* automatically copied over to prefs.js during startup of Firefox. To be
|
||||
* able to restore the original values of preferences a backup of prefs.js
|
||||
* will be created.
|
||||
*/
|
||||
async function syncPreferences(options: ProfileOptions): Promise<void> {
|
||||
const prefsPath = path.join(options.path, 'prefs.js');
|
||||
const userPath = path.join(options.path, 'user.js');
|
||||
|
||||
const lines = Object.entries(options.preferences).map(([key, value]) => {
|
||||
return `user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`;
|
||||
});
|
||||
|
||||
// Use allSettled to prevent corruption.
|
||||
const result = await Promise.allSettled([
|
||||
backupFile(userPath).then(async () => {
|
||||
await fs.promises.writeFile(userPath, lines.join('\n'));
|
||||
}),
|
||||
backupFile(prefsPath),
|
||||
]);
|
||||
for (const command of result) {
|
||||
if (command.status === 'rejected') {
|
||||
throw command.reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function compareVersions(a: string, b: string): number {
|
||||
// TODO: this is a not very reliable check.
|
||||
return parseInt(a.replace('.', ''), 16) - parseInt(b.replace('.', ''), 16);
|
||||
}
|
||||
70
node_modules/@puppeteer/browsers/src/browser-data/types.ts
generated
vendored
Normal file
70
node_modules/@puppeteer/browsers/src/browser-data/types.ts
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported browsers.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum Browser {
|
||||
CHROME = 'chrome',
|
||||
CHROMEHEADLESSSHELL = 'chrome-headless-shell',
|
||||
CHROMIUM = 'chromium',
|
||||
FIREFOX = 'firefox',
|
||||
CHROMEDRIVER = 'chromedriver',
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform names used to identify a OS platform x architecture combination in the way
|
||||
* that is relevant for the browser download.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum BrowserPlatform {
|
||||
LINUX = 'linux',
|
||||
LINUX_ARM = 'linux_arm',
|
||||
MAC = 'mac',
|
||||
MAC_ARM = 'mac_arm',
|
||||
WIN32 = 'win32',
|
||||
WIN64 = 'win64',
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum describing a release channel for a browser.
|
||||
*
|
||||
* You can use this in combination with {@link resolveBuildId} to resolve
|
||||
* a build ID based on a release channel.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export enum BrowserTag {
|
||||
CANARY = 'canary',
|
||||
NIGHTLY = 'nightly',
|
||||
BETA = 'beta',
|
||||
DEV = 'dev',
|
||||
DEVEDITION = 'devedition',
|
||||
STABLE = 'stable',
|
||||
ESR = 'esr',
|
||||
LATEST = 'latest',
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ProfileOptions {
|
||||
preferences: Record<string, unknown>;
|
||||
path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export enum ChromeReleaseChannel {
|
||||
STABLE = 'stable',
|
||||
DEV = 'dev',
|
||||
CANARY = 'canary',
|
||||
BETA = 'beta',
|
||||
}
|
||||
9
node_modules/@puppeteer/browsers/src/debug.ts
generated
vendored
Normal file
9
node_modules/@puppeteer/browsers/src/debug.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import debug from 'debug';
|
||||
|
||||
export {debug};
|
||||
52
node_modules/@puppeteer/browsers/src/detectPlatform.ts
generated
vendored
Normal file
52
node_modules/@puppeteer/browsers/src/detectPlatform.ts
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import os from 'node:os';
|
||||
|
||||
import {BrowserPlatform} from './browser-data/browser-data.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function detectBrowserPlatform(): BrowserPlatform | undefined {
|
||||
const platform = os.platform();
|
||||
const arch = os.arch();
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return arch === 'arm64' ? BrowserPlatform.MAC_ARM : BrowserPlatform.MAC;
|
||||
case 'linux':
|
||||
return arch === 'arm64'
|
||||
? BrowserPlatform.LINUX_ARM
|
||||
: BrowserPlatform.LINUX;
|
||||
case 'win32':
|
||||
return arch === 'x64' ||
|
||||
// Windows 11 for ARM supports x64 emulation
|
||||
(arch === 'arm64' && isWindows11(os.release()))
|
||||
? BrowserPlatform.WIN64
|
||||
: BrowserPlatform.WIN32;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows 11 is identified by the version 10.0.22000 or greater
|
||||
* @internal
|
||||
*/
|
||||
function isWindows11(version: string): boolean {
|
||||
const parts = version.split('.');
|
||||
if (parts.length > 2) {
|
||||
const major = parseInt(parts[0] as string, 10);
|
||||
const minor = parseInt(parts[1] as string, 10);
|
||||
const patch = parseInt(parts[2] as string, 10);
|
||||
return (
|
||||
major > 10 ||
|
||||
(major === 10 && minor > 0) ||
|
||||
(major === 10 && minor === 0 && patch >= 22000)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
183
node_modules/@puppeteer/browsers/src/fileUtil.ts
generated
vendored
Normal file
183
node_modules/@puppeteer/browsers/src/fileUtil.ts
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {ChildProcessByStdio} from 'node:child_process';
|
||||
import {spawnSync, spawn} from 'node:child_process';
|
||||
import {createReadStream} from 'node:fs';
|
||||
import {mkdir, readdir} from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import type {Readable, Transform, Writable} from 'node:stream';
|
||||
import {Stream} from 'node:stream';
|
||||
|
||||
import debug from 'debug';
|
||||
|
||||
const debugFileUtil = debug('puppeteer:browsers:fileUtil');
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function unpackArchive(
|
||||
archivePath: string,
|
||||
folderPath: string,
|
||||
): Promise<void> {
|
||||
if (!path.isAbsolute(folderPath)) {
|
||||
folderPath = path.resolve(process.cwd(), folderPath);
|
||||
}
|
||||
if (archivePath.endsWith('.zip')) {
|
||||
const extractZip = await import('extract-zip');
|
||||
await extractZip.default(archivePath, {dir: folderPath});
|
||||
} else if (archivePath.endsWith('.tar.bz2')) {
|
||||
await extractTar(archivePath, folderPath, 'bzip2');
|
||||
} else if (archivePath.endsWith('.dmg')) {
|
||||
await mkdir(folderPath);
|
||||
await installDMG(archivePath, folderPath);
|
||||
} else if (archivePath.endsWith('.exe')) {
|
||||
// Firefox on Windows.
|
||||
const result = spawnSync(archivePath, [`/ExtractDir=${folderPath}`], {
|
||||
env: {
|
||||
__compat_layer: 'RunAsInvoker',
|
||||
},
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`Failed to extract ${archivePath} to ${folderPath}: ${result.output}`,
|
||||
);
|
||||
}
|
||||
} else if (archivePath.endsWith('.tar.xz')) {
|
||||
await extractTar(archivePath, folderPath, 'xz');
|
||||
} else {
|
||||
throw new Error(`Unsupported archive format: ${archivePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function createTransformStream(
|
||||
child: ChildProcessByStdio<Writable, Readable, null>,
|
||||
): Transform {
|
||||
const stream = new Stream.Transform({
|
||||
transform(chunk, encoding, callback) {
|
||||
if (!child.stdin.write(chunk, encoding)) {
|
||||
child.stdin.once('drain', callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
flush(callback) {
|
||||
if (child.stdout.destroyed) {
|
||||
callback();
|
||||
} else {
|
||||
child.stdin.end();
|
||||
child.stdout.on('close', callback);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
child.stdin.on('error', e => {
|
||||
if ('code' in e && e.code === 'EPIPE') {
|
||||
// finished before reading the file finished (i.e. head)
|
||||
stream.emit('end');
|
||||
} else {
|
||||
stream.destroy(e);
|
||||
}
|
||||
});
|
||||
|
||||
child.stdout
|
||||
.on('data', data => {
|
||||
return stream.push(data);
|
||||
})
|
||||
.on('error', e => {
|
||||
return stream.destroy(e);
|
||||
});
|
||||
|
||||
child.once('close', () => {
|
||||
return stream.end();
|
||||
});
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const internalConstantsForTesting = {
|
||||
xz: 'xz',
|
||||
bzip2: 'bzip2',
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async function extractTar(
|
||||
tarPath: string,
|
||||
folderPath: string,
|
||||
decompressUtilityName: keyof typeof internalConstantsForTesting,
|
||||
): Promise<void> {
|
||||
const tarFs = await import('tar-fs');
|
||||
return await new Promise<void>((fulfill, reject) => {
|
||||
function handleError(utilityName: string) {
|
||||
return (error: Error) => {
|
||||
if ('code' in error && error.code === 'ENOENT') {
|
||||
error = new Error(
|
||||
`\`${utilityName}\` utility is required to unpack this archive`,
|
||||
{
|
||||
cause: error,
|
||||
},
|
||||
);
|
||||
}
|
||||
reject(error);
|
||||
};
|
||||
}
|
||||
const unpack = spawn(
|
||||
internalConstantsForTesting[decompressUtilityName],
|
||||
['-d'],
|
||||
{
|
||||
stdio: ['pipe', 'pipe', 'inherit'],
|
||||
},
|
||||
)
|
||||
.once('error', handleError(decompressUtilityName))
|
||||
.once('exit', code => {
|
||||
debugFileUtil(`${decompressUtilityName} exited, code=${code}`);
|
||||
});
|
||||
|
||||
const tar = tarFs.extract(folderPath);
|
||||
tar.once('error', handleError('tar'));
|
||||
tar.once('finish', fulfill);
|
||||
createReadStream(tarPath).pipe(createTransformStream(unpack)).pipe(tar);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async function installDMG(dmgPath: string, folderPath: string): Promise<void> {
|
||||
const {stdout} = spawnSync(`hdiutil`, [
|
||||
'attach',
|
||||
'-nobrowse',
|
||||
'-noautoopen',
|
||||
dmgPath,
|
||||
]);
|
||||
|
||||
const volumes = stdout.toString('utf8').match(/\/Volumes\/(.*)/m);
|
||||
if (!volumes) {
|
||||
throw new Error(`Could not find volume path in ${stdout}`);
|
||||
}
|
||||
const mountPath = volumes[0]!;
|
||||
|
||||
try {
|
||||
const fileNames = await readdir(mountPath);
|
||||
const appName = fileNames.find(item => {
|
||||
return typeof item === 'string' && item.endsWith('.app');
|
||||
});
|
||||
if (!appName) {
|
||||
throw new Error(`Cannot find app in ${mountPath}`);
|
||||
}
|
||||
const mountedPath = path.join(mountPath!, appName);
|
||||
|
||||
spawnSync('cp', ['-R', mountedPath, folderPath]);
|
||||
} finally {
|
||||
spawnSync('hdiutil', ['detach', mountPath, '-quiet']);
|
||||
}
|
||||
}
|
||||
164
node_modules/@puppeteer/browsers/src/httpUtil.ts
generated
vendored
Normal file
164
node_modules/@puppeteer/browsers/src/httpUtil.ts
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {createWriteStream} from 'node:fs';
|
||||
import * as http from 'node:http';
|
||||
import * as https from 'node:https';
|
||||
import {URL, urlToHttpOptions} from 'node:url';
|
||||
|
||||
import {ProxyAgent} from 'proxy-agent';
|
||||
|
||||
export function headHttpRequest(url: URL): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const request = httpRequest(
|
||||
url,
|
||||
'HEAD',
|
||||
response => {
|
||||
// consume response data free node process
|
||||
response.resume();
|
||||
resolve(response.statusCode === 200);
|
||||
},
|
||||
false,
|
||||
);
|
||||
request.on('error', () => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function httpRequest(
|
||||
url: URL,
|
||||
method: string,
|
||||
response: (x: http.IncomingMessage) => void,
|
||||
keepAlive = true,
|
||||
): http.ClientRequest {
|
||||
const options: http.RequestOptions = {
|
||||
protocol: url.protocol,
|
||||
hostname: url.hostname,
|
||||
port: url.port,
|
||||
path: url.pathname + url.search,
|
||||
method,
|
||||
headers: keepAlive ? {Connection: 'keep-alive'} : undefined,
|
||||
auth: urlToHttpOptions(url).auth,
|
||||
agent: new ProxyAgent(),
|
||||
};
|
||||
|
||||
const requestCallback = (res: http.IncomingMessage): void => {
|
||||
if (
|
||||
res.statusCode &&
|
||||
res.statusCode >= 300 &&
|
||||
res.statusCode < 400 &&
|
||||
res.headers.location
|
||||
) {
|
||||
httpRequest(new URL(res.headers.location), method, response);
|
||||
// consume response data to free up memory
|
||||
// And prevents the connection from being kept alive
|
||||
res.resume();
|
||||
} else {
|
||||
response(res);
|
||||
}
|
||||
};
|
||||
const request =
|
||||
options.protocol === 'https:'
|
||||
? https.request(options, requestCallback)
|
||||
: http.request(options, requestCallback);
|
||||
request.end();
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function downloadFile(
|
||||
url: URL,
|
||||
destinationPath: string,
|
||||
progressCallback?: (downloadedBytes: number, totalBytes: number) => void,
|
||||
): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let downloadedBytes = 0;
|
||||
let totalBytes = 0;
|
||||
|
||||
function onData(chunk: string): void {
|
||||
downloadedBytes += chunk.length;
|
||||
progressCallback!(downloadedBytes, totalBytes);
|
||||
}
|
||||
|
||||
const request = httpRequest(url, 'GET', response => {
|
||||
if (response.statusCode !== 200) {
|
||||
const error = new Error(
|
||||
`Download failed: server returned code ${response.statusCode}. URL: ${url}`,
|
||||
);
|
||||
// consume response data to free up memory
|
||||
response.resume();
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
const file = createWriteStream(destinationPath);
|
||||
file.on('close', () => {
|
||||
// The 'close' event is emitted when the stream and any of its
|
||||
// underlying resources (a file descriptor, for example) have been
|
||||
// closed. The event indicates that no more events will be emitted, and
|
||||
// no further computation will occur.
|
||||
return resolve();
|
||||
});
|
||||
file.on('error', error => {
|
||||
// The 'error' event may be emitted by a Readable implementation at any
|
||||
// time. Typically, this may occur if the underlying stream is unable to
|
||||
// generate data due to an underlying internal failure, or when a stream
|
||||
// implementation attempts to push an invalid chunk of data.
|
||||
return reject(error);
|
||||
});
|
||||
response.pipe(file);
|
||||
totalBytes = parseInt(response.headers['content-length']!, 10);
|
||||
if (progressCallback) {
|
||||
response.on('data', onData);
|
||||
}
|
||||
});
|
||||
request.on('error', error => {
|
||||
return reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getJSON(url: URL): Promise<unknown> {
|
||||
const text = await getText(url);
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error('Could not parse JSON from ' + url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export function getText(url: URL): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = httpRequest(
|
||||
url,
|
||||
'GET',
|
||||
response => {
|
||||
let data = '';
|
||||
if (response.statusCode && response.statusCode >= 400) {
|
||||
return reject(new Error(`Got status code ${response.statusCode}`));
|
||||
}
|
||||
response.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
response.on('end', () => {
|
||||
try {
|
||||
return resolve(String(data));
|
||||
} catch {
|
||||
return reject(
|
||||
new Error(`Failed to read text response from ${url}`),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
false,
|
||||
);
|
||||
request.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
654
node_modules/@puppeteer/browsers/src/install.ts
generated
vendored
Normal file
654
node_modules/@puppeteer/browsers/src/install.ts
generated
vendored
Normal file
@@ -0,0 +1,654 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import assert from 'node:assert';
|
||||
import {spawnSync} from 'node:child_process';
|
||||
import {existsSync, readFileSync} from 'node:fs';
|
||||
import {mkdir, unlink} from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import type * as ProgressBar from 'progress';
|
||||
import ProgressBarClass from 'progress';
|
||||
|
||||
import {
|
||||
Browser,
|
||||
BrowserPlatform,
|
||||
downloadUrls,
|
||||
} from './browser-data/browser-data.js';
|
||||
import {Cache, InstalledBrowser} from './Cache.js';
|
||||
import {debug} from './debug.js';
|
||||
import {DefaultProvider} from './DefaultProvider.js';
|
||||
import {detectBrowserPlatform} from './detectPlatform.js';
|
||||
import {unpackArchive} from './fileUtil.js';
|
||||
import {downloadFile, headHttpRequest} from './httpUtil.js';
|
||||
import type {BrowserProvider} from './provider.js';
|
||||
|
||||
const debugInstall = debug('puppeteer:browsers:install');
|
||||
|
||||
const times = new Map<string, [number, number]>();
|
||||
function debugTime(label: string) {
|
||||
times.set(label, process.hrtime());
|
||||
}
|
||||
|
||||
function debugTimeEnd(label: string) {
|
||||
const end = process.hrtime();
|
||||
const start = times.get(label);
|
||||
if (!start) {
|
||||
return;
|
||||
}
|
||||
const duration =
|
||||
end[0] * 1000 + end[1] / 1e6 - (start[0] * 1000 + start[1] / 1e6); // calculate duration in milliseconds
|
||||
debugInstall(`Duration for ${label}: ${duration}ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface InstallOptions {
|
||||
/**
|
||||
* Determines the path to download browsers to.
|
||||
*/
|
||||
cacheDir: string;
|
||||
/**
|
||||
* Determines which platform the browser will be suited for.
|
||||
*
|
||||
* @defaultValue **Auto-detected.**
|
||||
*/
|
||||
platform?: BrowserPlatform;
|
||||
/**
|
||||
* Determines which browser to install.
|
||||
*/
|
||||
browser: Browser;
|
||||
/**
|
||||
* Determines which buildId to download. BuildId should uniquely identify
|
||||
* binaries and they are used for caching.
|
||||
*/
|
||||
buildId: string;
|
||||
/**
|
||||
* An alias for the provided `buildId`. It will be used to maintain local
|
||||
* metadata to support aliases in the `launch` command.
|
||||
*
|
||||
* @example 'canary'
|
||||
*/
|
||||
buildIdAlias?: string;
|
||||
/**
|
||||
* Provides information about the progress of the download. If set to
|
||||
* 'default', the default callback implementing a progress bar will be
|
||||
* used.
|
||||
*/
|
||||
downloadProgressCallback?:
|
||||
| 'default'
|
||||
| ((downloadedBytes: number, totalBytes: number) => void);
|
||||
/**
|
||||
* Determines the host that will be used for downloading.
|
||||
*
|
||||
* @defaultValue Either
|
||||
*
|
||||
* - https://storage.googleapis.com/chrome-for-testing-public or
|
||||
* - https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central
|
||||
*
|
||||
*/
|
||||
baseUrl?: string;
|
||||
/**
|
||||
* Whether to unpack and install browser archives.
|
||||
*
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
unpack?: boolean;
|
||||
/**
|
||||
* @internal
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
forceFallbackForTesting?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to attempt to install system-level dependencies required
|
||||
* for the browser.
|
||||
*
|
||||
* Only supported for Chrome on Debian or Ubuntu.
|
||||
* Requires system-level privileges to run `apt-get`.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
installDeps?: boolean;
|
||||
/**
|
||||
* Custom provider implementation for alternative download sources.
|
||||
*
|
||||
* If not provided, uses the default provider.
|
||||
* Multiple providers can be chained - they will be tried in order.
|
||||
* The default provider is automatically added as the final fallback.
|
||||
*
|
||||
* ⚠️ **IMPORTANT**: Custom providers are NOT officially supported by
|
||||
* Puppeteer.
|
||||
*
|
||||
* By using custom providers, you accept full responsibility for:
|
||||
*
|
||||
* - **Version compatibility**: Different platforms may receive different
|
||||
* binary versions
|
||||
* - **Archive compatibility**: Binary structure must match Puppeteer's expectations
|
||||
* - **Feature integration**: Browser launch and other Puppeteer features may not work
|
||||
* - **Testing**: You must validate that downloaded binaries work with Puppeteer
|
||||
*
|
||||
* **Puppeteer only tests and guarantees compatibility with default binaries.**
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```typescript
|
||||
* import {ElectronProvider} from './puppeteer-browser-provider-electron.js';
|
||||
*
|
||||
* await install({
|
||||
* browser: Browser.CHROMEDRIVER,
|
||||
* buildId: '142.0.7444.175',
|
||||
* cacheDir: './cache',
|
||||
* providers: [
|
||||
* new ElectronProvider(), // Try Electron releases first
|
||||
* // Falls back to Chrome for Testing automatically
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
providers?: BrowserProvider[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Install using custom provider plugins.
|
||||
* Tries each provider in order until one succeeds.
|
||||
* Falls back to default provider if all custom providers fail.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
async function installWithProviders(
|
||||
options: InstallOptions,
|
||||
): Promise<InstalledBrowser | string> {
|
||||
if (!options.platform) {
|
||||
throw new Error('Platform must be defined');
|
||||
}
|
||||
|
||||
const cache = new Cache(options.cacheDir);
|
||||
const browserRoot = cache.browserRoot(options.browser);
|
||||
|
||||
// Build provider list with proper fallback behavior
|
||||
const providers = [...(options.providers || [])];
|
||||
|
||||
// If custom baseUrl is provided, add it as a provider
|
||||
if (options.baseUrl) {
|
||||
providers.push(new DefaultProvider(options.baseUrl));
|
||||
}
|
||||
|
||||
// Always add default provider as final fallback
|
||||
// (unless custom baseUrl is provided and forceFallbackForTesting is false)
|
||||
if (!options.baseUrl || options.forceFallbackForTesting) {
|
||||
providers.push(new DefaultProvider());
|
||||
}
|
||||
|
||||
const downloadOptions = {
|
||||
browser: options.browser,
|
||||
platform: options.platform,
|
||||
buildId: options.buildId,
|
||||
progressCallback:
|
||||
options.downloadProgressCallback === 'default'
|
||||
? await makeProgressCallback(
|
||||
options.browser,
|
||||
options.buildIdAlias ?? options.buildId,
|
||||
)
|
||||
: options.downloadProgressCallback,
|
||||
};
|
||||
|
||||
interface ProviderError {
|
||||
providerName: string;
|
||||
error: Error;
|
||||
}
|
||||
|
||||
const errors: ProviderError[] = [];
|
||||
|
||||
for (const provider of providers) {
|
||||
try {
|
||||
// Check: does this provider support this browser/platform?
|
||||
if (!(await provider.supports(downloadOptions))) {
|
||||
debugInstall(
|
||||
`Provider ${provider.getName()} does not support ${options.browser} on ${options.platform}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Warn if using non-default provider
|
||||
if (!(provider instanceof DefaultProvider)) {
|
||||
debugInstall(`⚠️ Using custom downloader: ${provider.getName()}`);
|
||||
debugInstall(
|
||||
`⚠️ Puppeteer does not guarantee compatibility with non-default providers`,
|
||||
);
|
||||
}
|
||||
|
||||
debugInstall(
|
||||
`Trying provider: ${provider.getName()} for ${options.browser} ${options.buildId}`,
|
||||
);
|
||||
|
||||
// Get download URL from provider
|
||||
const url = await provider.getDownloadUrl(downloadOptions);
|
||||
if (!url) {
|
||||
debugInstall(
|
||||
`Provider ${provider.getName()} returned no URL for ${options.browser} ${options.buildId}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
debugInstall(`Successfully got URL from ${provider.getName()}: ${url}`);
|
||||
|
||||
if (!existsSync(browserRoot)) {
|
||||
await mkdir(browserRoot, {recursive: true});
|
||||
}
|
||||
|
||||
// Download and install using the URL from the provider
|
||||
return await installUrl(url, options, provider);
|
||||
} catch (err) {
|
||||
debugInstall(
|
||||
`Provider ${provider.getName()} failed: ${(err as Error).message}`,
|
||||
);
|
||||
errors.push({
|
||||
providerName: provider.getName(),
|
||||
error: err as Error,
|
||||
});
|
||||
// Continue to next provider
|
||||
}
|
||||
}
|
||||
|
||||
// All providers failed
|
||||
const errorDetails = errors
|
||||
.map(e => {
|
||||
return ` - ${e.providerName}: ${e.error.message}`;
|
||||
})
|
||||
.join('\n');
|
||||
throw new Error(
|
||||
`All providers failed for ${options.browser} ${options.buildId}:\n${errorDetails}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and unpacks the browser archive according to the
|
||||
* {@link InstallOptions}.
|
||||
*
|
||||
* @returns a {@link InstalledBrowser} instance.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function install(
|
||||
options: InstallOptions & {unpack?: true},
|
||||
): Promise<InstalledBrowser>;
|
||||
/**
|
||||
* Downloads the browser archive according to the {@link InstallOptions} without
|
||||
* unpacking.
|
||||
*
|
||||
* @returns the absolute path to the archive.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function install(
|
||||
options: InstallOptions & {unpack: false},
|
||||
): Promise<string>;
|
||||
export async function install(
|
||||
options: InstallOptions,
|
||||
): Promise<InstalledBrowser | string> {
|
||||
options.platform ??= detectBrowserPlatform();
|
||||
options.unpack ??= true;
|
||||
if (!options.platform) {
|
||||
throw new Error(
|
||||
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`,
|
||||
);
|
||||
}
|
||||
|
||||
// Always use plugin architecture (uses default provider if none specified)
|
||||
options.providers ??= [];
|
||||
return await installWithProviders(options);
|
||||
}
|
||||
|
||||
async function installDeps(installedBrowser: InstalledBrowser) {
|
||||
if (
|
||||
process.platform !== 'linux' ||
|
||||
installedBrowser.platform !== BrowserPlatform.LINUX
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Currently, only Debian-like deps are supported.
|
||||
const depsPath = path.join(
|
||||
path.dirname(installedBrowser.executablePath),
|
||||
'deb.deps',
|
||||
);
|
||||
if (!existsSync(depsPath)) {
|
||||
debugInstall(`deb.deps file was not found at ${depsPath}`);
|
||||
return;
|
||||
}
|
||||
const data = readFileSync(depsPath, 'utf-8').split('\n').join(',');
|
||||
if (process.getuid?.() !== 0) {
|
||||
throw new Error('Installing system dependencies requires root privileges');
|
||||
}
|
||||
let result = spawnSync('apt-get', ['-v']);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
'Failed to install system dependencies: apt-get does not seem to be available',
|
||||
);
|
||||
}
|
||||
debugInstall(`Trying to install dependencies: ${data}`);
|
||||
result = spawnSync('apt-get', [
|
||||
'satisfy',
|
||||
'-y',
|
||||
data,
|
||||
'--no-install-recommends',
|
||||
]);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`Failed to install system dependencies: status=${result.status},error=${result.error},stdout=${result.stdout.toString('utf8')},stderr=${result.stderr.toString('utf8')}`,
|
||||
);
|
||||
}
|
||||
debugInstall(`Installed system dependencies ${data}`);
|
||||
}
|
||||
|
||||
async function installUrl(
|
||||
url: URL,
|
||||
options: InstallOptions,
|
||||
provider: BrowserProvider,
|
||||
): Promise<InstalledBrowser | string> {
|
||||
if (!provider) {
|
||||
throw new Error('Provider is required for installation');
|
||||
}
|
||||
options.platform ??= detectBrowserPlatform();
|
||||
if (!options.platform) {
|
||||
throw new Error(
|
||||
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`,
|
||||
);
|
||||
}
|
||||
let downloadProgressCallback = options.downloadProgressCallback;
|
||||
if (downloadProgressCallback === 'default') {
|
||||
downloadProgressCallback = await makeProgressCallback(
|
||||
options.browser,
|
||||
options.buildIdAlias ?? options.buildId,
|
||||
);
|
||||
}
|
||||
const fileName = decodeURIComponent(url.toString()).split('/').pop();
|
||||
assert(fileName, `A malformed download URL was found: ${url}.`);
|
||||
const cache = new Cache(options.cacheDir);
|
||||
const browserRoot = cache.browserRoot(options.browser);
|
||||
const archivePath = path.join(browserRoot, `${options.buildId}-${fileName}`);
|
||||
if (!existsSync(browserRoot)) {
|
||||
await mkdir(browserRoot, {recursive: true});
|
||||
}
|
||||
|
||||
if (!options.unpack) {
|
||||
if (existsSync(archivePath)) {
|
||||
return archivePath;
|
||||
}
|
||||
debugInstall(`Downloading binary from ${url}`);
|
||||
debugTime('download');
|
||||
await downloadFile(url, archivePath, downloadProgressCallback);
|
||||
debugTimeEnd('download');
|
||||
return archivePath;
|
||||
}
|
||||
|
||||
const outputPath = cache.installationDir(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.buildId,
|
||||
);
|
||||
|
||||
// Get executable path from provider once (used for both cached and new installations)
|
||||
const relativeExecutablePath = await provider.getExecutablePath({
|
||||
browser: options.browser,
|
||||
buildId: options.buildId,
|
||||
platform: options.platform,
|
||||
});
|
||||
debugInstall(
|
||||
`Using executable path from provider: ${relativeExecutablePath}`,
|
||||
);
|
||||
|
||||
const installedBrowser = new InstalledBrowser(
|
||||
cache,
|
||||
options.browser,
|
||||
options.buildId,
|
||||
options.platform,
|
||||
);
|
||||
|
||||
// Write metadata for the installation (only for non-default providers)
|
||||
if (!(provider instanceof DefaultProvider)) {
|
||||
cache.writeExecutablePath(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.buildId,
|
||||
relativeExecutablePath,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (existsSync(outputPath)) {
|
||||
if (!existsSync(installedBrowser.executablePath)) {
|
||||
throw new Error(
|
||||
`The browser folder (${outputPath}) exists but the executable (${installedBrowser.executablePath}) is missing`,
|
||||
);
|
||||
}
|
||||
await runSetup(installedBrowser);
|
||||
if (options.installDeps) {
|
||||
await installDeps(installedBrowser);
|
||||
}
|
||||
return installedBrowser;
|
||||
}
|
||||
|
||||
// Check if archive already exists (e.g., from a custom provider)
|
||||
if (!existsSync(archivePath)) {
|
||||
debugInstall(`Downloading binary from ${url}`);
|
||||
try {
|
||||
debugTime('download');
|
||||
await downloadFile(url, archivePath, downloadProgressCallback);
|
||||
} finally {
|
||||
debugTimeEnd('download');
|
||||
}
|
||||
} else {
|
||||
debugInstall(`Using existing archive at ${archivePath}`);
|
||||
}
|
||||
|
||||
debugInstall(`Installing ${archivePath} to ${outputPath}`);
|
||||
try {
|
||||
debugTime('extract');
|
||||
await unpackArchive(archivePath, outputPath);
|
||||
} finally {
|
||||
debugTimeEnd('extract');
|
||||
}
|
||||
|
||||
if (options.buildIdAlias) {
|
||||
const metadata = installedBrowser.readMetadata();
|
||||
metadata.aliases[options.buildIdAlias] = options.buildId;
|
||||
installedBrowser.writeMetadata(metadata);
|
||||
}
|
||||
|
||||
await runSetup(installedBrowser);
|
||||
if (options.installDeps) {
|
||||
await installDeps(installedBrowser);
|
||||
}
|
||||
return installedBrowser;
|
||||
} finally {
|
||||
if (existsSync(archivePath)) {
|
||||
await unlink(archivePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function runSetup(installedBrowser: InstalledBrowser): Promise<void> {
|
||||
// On Windows for Chrome invoke setup.exe to configure sandboxes.
|
||||
if (
|
||||
(installedBrowser.platform === BrowserPlatform.WIN32 ||
|
||||
installedBrowser.platform === BrowserPlatform.WIN64) &&
|
||||
installedBrowser.browser === Browser.CHROME &&
|
||||
installedBrowser.platform === detectBrowserPlatform()
|
||||
) {
|
||||
try {
|
||||
debugTime('permissions');
|
||||
const browserDir = path.dirname(installedBrowser.executablePath);
|
||||
const setupExePath = path.join(browserDir, 'setup.exe');
|
||||
if (!existsSync(setupExePath)) {
|
||||
return;
|
||||
}
|
||||
spawnSync(
|
||||
path.join(browserDir, 'setup.exe'),
|
||||
[`--configure-browser-in-directory=` + browserDir],
|
||||
{
|
||||
shell: true,
|
||||
},
|
||||
);
|
||||
// TODO: Handle error here. Currently the setup.exe sometimes
|
||||
// errors although it sets the permissions correctly.
|
||||
} finally {
|
||||
debugTimeEnd('permissions');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface UninstallOptions {
|
||||
/**
|
||||
* Determines the platform for the browser binary.
|
||||
*
|
||||
* @defaultValue **Auto-detected.**
|
||||
*/
|
||||
platform?: BrowserPlatform;
|
||||
/**
|
||||
* The path to the root of the cache directory.
|
||||
*/
|
||||
cacheDir: string;
|
||||
/**
|
||||
* Determines which browser to uninstall.
|
||||
*/
|
||||
browser: Browser;
|
||||
/**
|
||||
* The browser build to uninstall
|
||||
*/
|
||||
buildId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export async function uninstall(options: UninstallOptions): Promise<void> {
|
||||
options.platform ??= detectBrowserPlatform();
|
||||
if (!options.platform) {
|
||||
throw new Error(
|
||||
`Cannot detect the browser platform for: ${os.platform()} (${os.arch()})`,
|
||||
);
|
||||
}
|
||||
|
||||
new Cache(options.cacheDir).uninstall(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.buildId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface GetInstalledBrowsersOptions {
|
||||
/**
|
||||
* The path to the root of the cache directory.
|
||||
*/
|
||||
cacheDir: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metadata about browsers installed in the cache directory.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export async function getInstalledBrowsers(
|
||||
options: GetInstalledBrowsersOptions,
|
||||
): Promise<InstalledBrowser[]> {
|
||||
return new Cache(options.cacheDir).getInstalledBrowsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function canDownload(options: InstallOptions): Promise<boolean> {
|
||||
options.platform ??= detectBrowserPlatform();
|
||||
if (!options.platform) {
|
||||
throw new Error(
|
||||
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`,
|
||||
);
|
||||
}
|
||||
|
||||
// Always use plugin architecture (uses default provider if none specified)
|
||||
const providers = [
|
||||
...(options.providers || []),
|
||||
new DefaultProvider(options.baseUrl),
|
||||
];
|
||||
|
||||
const downloadOptions = {
|
||||
browser: options.browser,
|
||||
platform: options.platform,
|
||||
buildId: options.buildId,
|
||||
};
|
||||
|
||||
// Check if any provider can provide a valid, downloadable URL
|
||||
for (const provider of providers) {
|
||||
if (!(await provider.supports(downloadOptions))) {
|
||||
continue;
|
||||
}
|
||||
const url = await provider.getDownloadUrl(downloadOptions);
|
||||
if (url && (await headHttpRequest(url))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a URL for downloading the binary archive of a given browser.
|
||||
*
|
||||
* The archive is bound to the specific platform and build ID specified.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function getDownloadUrl(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
baseUrl?: string,
|
||||
): URL {
|
||||
return new URL(downloadUrls[browser](platform, buildId, baseUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function makeProgressCallback(
|
||||
browser: Browser,
|
||||
buildId: string,
|
||||
): (downloadedBytes: number, totalBytes: number) => void {
|
||||
let progressBar: ProgressBar;
|
||||
|
||||
let lastDownloadedBytes = 0;
|
||||
return (downloadedBytes: number, totalBytes: number) => {
|
||||
if (!progressBar) {
|
||||
progressBar = new ProgressBarClass(
|
||||
`Downloading ${browser} ${buildId} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `,
|
||||
{
|
||||
complete: '=',
|
||||
incomplete: ' ',
|
||||
width: 20,
|
||||
total: totalBytes,
|
||||
},
|
||||
);
|
||||
}
|
||||
const delta = downloadedBytes - lastDownloadedBytes;
|
||||
lastDownloadedBytes = downloadedBytes;
|
||||
progressBar.tick(delta);
|
||||
};
|
||||
}
|
||||
|
||||
function toMegabytes(bytes: number) {
|
||||
const mb = bytes / 1000 / 1000;
|
||||
return `${Math.round(mb * 10) / 10} MB`;
|
||||
}
|
||||
653
node_modules/@puppeteer/browsers/src/launch.ts
generated
vendored
Normal file
653
node_modules/@puppeteer/browsers/src/launch.ts
generated
vendored
Normal file
@@ -0,0 +1,653 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import childProcess from 'node:child_process';
|
||||
import {EventEmitter} from 'node:events';
|
||||
import {accessSync} from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import readline from 'node:readline';
|
||||
import type Stream from 'node:stream';
|
||||
|
||||
import {
|
||||
type Browser,
|
||||
type BrowserPlatform,
|
||||
type ChromeReleaseChannel,
|
||||
executablePathByBrowser,
|
||||
resolveSystemExecutablePaths,
|
||||
} from './browser-data/browser-data.js';
|
||||
import {Cache} from './Cache.js';
|
||||
import {debug} from './debug.js';
|
||||
import {detectBrowserPlatform} from './detectPlatform.js';
|
||||
|
||||
const debugLaunch = debug('puppeteer:browsers:launcher');
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ComputeExecutablePathOptions {
|
||||
/**
|
||||
* Root path to the storage directory.
|
||||
*
|
||||
* Can be set to `null` if the executable path should be relative
|
||||
* to the extracted download location. E.g. `./chrome-linux64/chrome`.
|
||||
*/
|
||||
cacheDir: string | null;
|
||||
/**
|
||||
* Determines which platform the browser will be suited for.
|
||||
*
|
||||
* @defaultValue **Auto-detected.**
|
||||
*/
|
||||
platform?: BrowserPlatform;
|
||||
/**
|
||||
* Determines which browser to launch.
|
||||
*/
|
||||
browser: Browser;
|
||||
/**
|
||||
* Determines which buildId to download. BuildId should uniquely identify
|
||||
* binaries and they are used for caching.
|
||||
*/
|
||||
buildId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function computeExecutablePath(
|
||||
options: ComputeExecutablePathOptions,
|
||||
): string {
|
||||
if (options.cacheDir === null) {
|
||||
options.platform ??= detectBrowserPlatform();
|
||||
if (options.platform === undefined) {
|
||||
throw new Error(
|
||||
`No platform specified. Couldn't auto-detect browser platform.`,
|
||||
);
|
||||
}
|
||||
return executablePathByBrowser[options.browser](
|
||||
options.platform,
|
||||
options.buildId,
|
||||
);
|
||||
}
|
||||
|
||||
return new Cache(options.cacheDir).computeExecutablePath(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface SystemOptions {
|
||||
/**
|
||||
* Determines which platform the browser will be suited for.
|
||||
*
|
||||
* @defaultValue **Auto-detected.**
|
||||
*/
|
||||
platform?: BrowserPlatform;
|
||||
/**
|
||||
* Determines which browser to launch.
|
||||
*/
|
||||
browser: Browser;
|
||||
/**
|
||||
* Release channel to look for on the system.
|
||||
*/
|
||||
channel: ChromeReleaseChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a path to a system-wide Chrome installation given a release channel
|
||||
* name by checking known installation locations (using
|
||||
* {@link https://pptr.dev/browsers-api/browsers.computesystemexecutablepath}).
|
||||
* If Chrome instance is not found at the expected path, an error is thrown.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function computeSystemExecutablePath(options: SystemOptions): string {
|
||||
options.platform ??= detectBrowserPlatform();
|
||||
if (!options.platform) {
|
||||
throw new Error(
|
||||
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`,
|
||||
);
|
||||
}
|
||||
const paths = resolveSystemExecutablePaths(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.channel,
|
||||
);
|
||||
for (const path of paths) {
|
||||
try {
|
||||
accessSync(path);
|
||||
return path;
|
||||
} catch {}
|
||||
}
|
||||
throw new Error(
|
||||
`Could not find Google Chrome executable for channel '${options.channel}' at:${paths.map(
|
||||
path => {
|
||||
return `\n - ${path}`;
|
||||
},
|
||||
)}.`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface LaunchOptions {
|
||||
/**
|
||||
* Absolute path to the browser's executable.
|
||||
*/
|
||||
executablePath: string;
|
||||
/**
|
||||
* Configures stdio streams to open two additional streams for automation over
|
||||
* those streams instead of WebSocket.
|
||||
*
|
||||
* @defaultValue `false`.
|
||||
*/
|
||||
pipe?: boolean;
|
||||
/**
|
||||
* If true, forwards the browser's process stdout and stderr to the Node's
|
||||
* process stdout and stderr.
|
||||
*
|
||||
* @defaultValue `false`.
|
||||
*/
|
||||
dumpio?: boolean;
|
||||
/**
|
||||
* Additional arguments to pass to the executable when launching.
|
||||
*/
|
||||
args?: string[];
|
||||
/**
|
||||
* Environment variables to set for the browser process.
|
||||
*/
|
||||
env?: Record<string, string | undefined>;
|
||||
/**
|
||||
* Handles SIGINT in the Node process and tries to kill the browser process.
|
||||
*
|
||||
* @defaultValue `true`.
|
||||
*/
|
||||
handleSIGINT?: boolean;
|
||||
/**
|
||||
* Handles SIGTERM in the Node process and tries to gracefully close the browser
|
||||
* process.
|
||||
*
|
||||
* @defaultValue `true`.
|
||||
*/
|
||||
handleSIGTERM?: boolean;
|
||||
/**
|
||||
* Handles SIGHUP in the Node process and tries to gracefully close the browser process.
|
||||
*
|
||||
* @defaultValue `true`.
|
||||
*/
|
||||
handleSIGHUP?: boolean;
|
||||
/**
|
||||
* Whether to spawn process in the {@link https://nodejs.org/api/child_process.html#optionsdetached | detached}
|
||||
* mode.
|
||||
*
|
||||
* @defaultValue `true` except on Windows.
|
||||
*/
|
||||
detached?: boolean;
|
||||
/**
|
||||
* A callback to run after the browser process exits or before the process
|
||||
* will be closed via the {@link Process.close} call (including when handling
|
||||
* signals). The callback is only run once.
|
||||
*/
|
||||
onExit?: () => Promise<void>;
|
||||
/**
|
||||
* If provided, the process will be killed when the signal is aborted.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches a browser process according to {@link LaunchOptions}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function launch(opts: LaunchOptions): Process {
|
||||
return new Process(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const CDP_WEBSOCKET_ENDPOINT_REGEX =
|
||||
/^DevTools listening on (ws:\/\/.*)$/;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX =
|
||||
/^WebDriver BiDi listening on (ws:\/\/.*)$/;
|
||||
|
||||
type EventHandler = (...args: any[]) => void;
|
||||
const processListeners = new Map<string, EventHandler[]>();
|
||||
const dispatchers = {
|
||||
exit: (...args: any[]) => {
|
||||
processListeners.get('exit')?.forEach(handler => {
|
||||
return handler(...args);
|
||||
});
|
||||
},
|
||||
SIGINT: (...args: any[]) => {
|
||||
processListeners.get('SIGINT')?.forEach(handler => {
|
||||
return handler(...args);
|
||||
});
|
||||
},
|
||||
SIGHUP: (...args: any[]) => {
|
||||
processListeners.get('SIGHUP')?.forEach(handler => {
|
||||
return handler(...args);
|
||||
});
|
||||
},
|
||||
SIGTERM: (...args: any[]) => {
|
||||
processListeners.get('SIGTERM')?.forEach(handler => {
|
||||
return handler(...args);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
function subscribeToProcessEvent(
|
||||
event: 'exit' | 'SIGINT' | 'SIGHUP' | 'SIGTERM',
|
||||
handler: EventHandler,
|
||||
): void {
|
||||
const listeners = processListeners.get(event) || [];
|
||||
if (listeners.length === 0) {
|
||||
process.on(event, dispatchers[event]);
|
||||
}
|
||||
listeners.push(handler);
|
||||
processListeners.set(event, listeners);
|
||||
}
|
||||
|
||||
function unsubscribeFromProcessEvent(
|
||||
event: 'exit' | 'SIGINT' | 'SIGHUP' | 'SIGTERM',
|
||||
handler: EventHandler,
|
||||
): void {
|
||||
const listeners = processListeners.get(event) || [];
|
||||
const existingListenerIdx = listeners.indexOf(handler);
|
||||
if (existingListenerIdx === -1) {
|
||||
return;
|
||||
}
|
||||
listeners.splice(existingListenerIdx, 1);
|
||||
processListeners.set(event, listeners);
|
||||
if (listeners.length === 0) {
|
||||
process.off(event, dispatchers[event]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class Process {
|
||||
#executablePath;
|
||||
#args: string[];
|
||||
#browserProcess: childProcess.ChildProcess;
|
||||
#exited = false;
|
||||
// The browser process can be closed externally or from the driver process. We
|
||||
// need to invoke the hooks only once though but we don't know how many times
|
||||
// we will be invoked.
|
||||
#hooksRan = false;
|
||||
#onExitHook = async () => {};
|
||||
#browserProcessExiting: Promise<void>;
|
||||
#logs: string[] = [];
|
||||
#maxLogLinesSize = 1000;
|
||||
#lineEmitter = new EventEmitter();
|
||||
#onAbort = (): void => {
|
||||
this.kill();
|
||||
};
|
||||
#signal?: AbortSignal;
|
||||
|
||||
constructor(opts: LaunchOptions) {
|
||||
this.#executablePath = opts.executablePath;
|
||||
this.#args = opts.args ?? [];
|
||||
|
||||
this.#signal = opts.signal;
|
||||
if (this.#signal?.aborted) {
|
||||
throw new Error(
|
||||
this.#signal.reason ? this.#signal.reason : 'Launch aborted',
|
||||
);
|
||||
}
|
||||
this.#signal?.addEventListener('abort', this.#onAbort, {once: true});
|
||||
|
||||
opts.pipe ??= false;
|
||||
opts.dumpio ??= false;
|
||||
opts.handleSIGINT ??= true;
|
||||
opts.handleSIGTERM ??= true;
|
||||
opts.handleSIGHUP ??= true;
|
||||
// On non-windows platforms, `detached: true` makes child process a
|
||||
// leader of a new process group, making it possible to kill child
|
||||
// process tree with `.kill(-pid)` command. @see
|
||||
// https://nodejs.org/api/child_process.html#child_process_options_detached
|
||||
opts.detached ??= process.platform !== 'win32';
|
||||
const stdio = this.#configureStdio({
|
||||
pipe: opts.pipe,
|
||||
});
|
||||
|
||||
const env = opts.env || {};
|
||||
|
||||
debugLaunch(`Launching ${this.#executablePath} ${this.#args.join(' ')}`, {
|
||||
detached: opts.detached,
|
||||
env: Object.keys(env).reduce<Record<string, string | undefined>>(
|
||||
(res, key) => {
|
||||
if (key.toLowerCase().startsWith('puppeteer_')) {
|
||||
res[key] = env[key];
|
||||
}
|
||||
return res;
|
||||
},
|
||||
{},
|
||||
),
|
||||
stdio,
|
||||
});
|
||||
|
||||
this.#browserProcess = childProcess.spawn(
|
||||
this.#executablePath,
|
||||
this.#args,
|
||||
{
|
||||
detached: opts.detached,
|
||||
env,
|
||||
stdio,
|
||||
},
|
||||
);
|
||||
this.#recordStream(this.#browserProcess.stderr!);
|
||||
this.#recordStream(this.#browserProcess.stdout!);
|
||||
|
||||
debugLaunch(`Launched ${this.#browserProcess.pid}`);
|
||||
if (opts.dumpio) {
|
||||
this.#browserProcess.stderr?.pipe(process.stderr);
|
||||
this.#browserProcess.stdout?.pipe(process.stdout);
|
||||
}
|
||||
subscribeToProcessEvent('exit', this.#onDriverProcessExit);
|
||||
if (opts.handleSIGINT) {
|
||||
subscribeToProcessEvent('SIGINT', this.#onDriverProcessSignal);
|
||||
}
|
||||
if (opts.handleSIGTERM) {
|
||||
subscribeToProcessEvent('SIGTERM', this.#onDriverProcessSignal);
|
||||
}
|
||||
if (opts.handleSIGHUP) {
|
||||
subscribeToProcessEvent('SIGHUP', this.#onDriverProcessSignal);
|
||||
}
|
||||
if (opts.onExit) {
|
||||
this.#onExitHook = opts.onExit;
|
||||
}
|
||||
this.#browserProcessExiting = new Promise((resolve, reject) => {
|
||||
this.#browserProcess.once('exit', async () => {
|
||||
debugLaunch(`Browser process ${this.#browserProcess.pid} onExit`);
|
||||
this.#clearListeners();
|
||||
this.#exited = true;
|
||||
try {
|
||||
await this.#runHooks();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async #runHooks() {
|
||||
if (this.#hooksRan) {
|
||||
return;
|
||||
}
|
||||
this.#hooksRan = true;
|
||||
await this.#onExitHook();
|
||||
}
|
||||
|
||||
get nodeProcess(): childProcess.ChildProcess {
|
||||
return this.#browserProcess;
|
||||
}
|
||||
|
||||
#configureStdio(opts: {pipe: boolean}): Array<'ignore' | 'pipe'> {
|
||||
if (opts.pipe) {
|
||||
return ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'];
|
||||
} else {
|
||||
return ['pipe', 'pipe', 'pipe'];
|
||||
}
|
||||
}
|
||||
|
||||
#clearListeners(): void {
|
||||
unsubscribeFromProcessEvent('exit', this.#onDriverProcessExit);
|
||||
unsubscribeFromProcessEvent('SIGINT', this.#onDriverProcessSignal);
|
||||
unsubscribeFromProcessEvent('SIGTERM', this.#onDriverProcessSignal);
|
||||
unsubscribeFromProcessEvent('SIGHUP', this.#onDriverProcessSignal);
|
||||
this.#signal?.removeEventListener('abort', this.#onAbort);
|
||||
}
|
||||
|
||||
#onDriverProcessExit = (_code: number) => {
|
||||
this.kill();
|
||||
};
|
||||
|
||||
#onDriverProcessSignal = (signal: string): void => {
|
||||
switch (signal) {
|
||||
case 'SIGINT':
|
||||
this.kill();
|
||||
process.exit(130);
|
||||
case 'SIGTERM':
|
||||
case 'SIGHUP':
|
||||
void this.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this.#runHooks();
|
||||
if (!this.#exited) {
|
||||
this.kill();
|
||||
}
|
||||
return await this.#browserProcessExiting;
|
||||
}
|
||||
|
||||
hasClosed(): Promise<void> {
|
||||
return this.#browserProcessExiting;
|
||||
}
|
||||
|
||||
kill(): void {
|
||||
debugLaunch(`Trying to kill ${this.#browserProcess.pid}`);
|
||||
// If the process failed to launch (for example if the browser executable path
|
||||
// is invalid), then the process does not get a pid assigned. A call to
|
||||
// `proc.kill` would error, as the `pid` to-be-killed can not be found.
|
||||
if (
|
||||
this.#browserProcess &&
|
||||
this.#browserProcess.pid &&
|
||||
pidExists(this.#browserProcess.pid)
|
||||
) {
|
||||
try {
|
||||
debugLaunch(`Browser process ${this.#browserProcess.pid} exists`);
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
childProcess.execSync(
|
||||
`taskkill /pid ${this.#browserProcess.pid} /T /F`,
|
||||
);
|
||||
} catch (error) {
|
||||
debugLaunch(
|
||||
`Killing ${this.#browserProcess.pid} using taskkill failed`,
|
||||
error,
|
||||
);
|
||||
// taskkill can fail to kill the process e.g. due to missing permissions.
|
||||
// Let's kill the process via Node API. This delays killing of all child
|
||||
// processes of `this.proc` until the main Node.js process dies.
|
||||
this.#browserProcess.kill();
|
||||
}
|
||||
} else {
|
||||
// on linux the process group can be killed with the group id prefixed with
|
||||
// a minus sign. The process group id is the group leader's pid.
|
||||
const processGroupId = -this.#browserProcess.pid;
|
||||
|
||||
try {
|
||||
process.kill(processGroupId, 'SIGKILL');
|
||||
} catch (error) {
|
||||
debugLaunch(
|
||||
`Killing ${this.#browserProcess.pid} using process.kill failed`,
|
||||
error,
|
||||
);
|
||||
// Killing the process group can fail due e.g. to missing permissions.
|
||||
// Let's kill the process via Node API. This delays killing of all child
|
||||
// processes of `this.proc` until the main Node.js process dies.
|
||||
this.#browserProcess.kill('SIGKILL');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`${PROCESS_ERROR_EXPLANATION}\nError cause: ${
|
||||
isErrorLike(error) ? error.stack : error
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.#clearListeners();
|
||||
}
|
||||
|
||||
#recordStream(stream: Stream.Readable): void {
|
||||
const rl = readline.createInterface(stream);
|
||||
const cleanup = (): void => {
|
||||
rl.off('line', onLine);
|
||||
rl.off('close', onClose);
|
||||
try {
|
||||
rl.close();
|
||||
} catch {}
|
||||
};
|
||||
const onLine = (line: string) => {
|
||||
if (line.trim() === '') {
|
||||
return;
|
||||
}
|
||||
this.#logs.push(line);
|
||||
const delta = this.#logs.length - this.#maxLogLinesSize;
|
||||
if (delta) {
|
||||
this.#logs.splice(0, delta);
|
||||
}
|
||||
this.#lineEmitter.emit('line', line);
|
||||
};
|
||||
const onClose = (): void => {
|
||||
cleanup();
|
||||
};
|
||||
rl.on('line', onLine);
|
||||
rl.on('close', onClose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent logs (stderr + stdout) emitted by the browser.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getRecentLogs(): string[] {
|
||||
return [...this.#logs];
|
||||
}
|
||||
|
||||
waitForLineOutput(regex: RegExp, timeout = 0): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onClose = (errorOrCode?: Error | number): void => {
|
||||
cleanup();
|
||||
reject(
|
||||
new Error(
|
||||
[
|
||||
`Failed to launch the browser process: ${
|
||||
errorOrCode instanceof Error
|
||||
? ` ${errorOrCode.message}`
|
||||
: ` Code: ${errorOrCode}`
|
||||
}`,
|
||||
'',
|
||||
`stderr:`,
|
||||
this.getRecentLogs().join('\n'),
|
||||
'',
|
||||
'TROUBLESHOOTING: https://pptr.dev/troubleshooting',
|
||||
'',
|
||||
].join('\n'),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
this.#browserProcess.on('exit', onClose);
|
||||
this.#browserProcess.on('error', onClose);
|
||||
const timeoutId =
|
||||
timeout > 0 ? setTimeout(onTimeout, timeout) : undefined;
|
||||
|
||||
this.#lineEmitter.on('line', onLine);
|
||||
const cleanup = (): void => {
|
||||
clearTimeout(timeoutId);
|
||||
this.#lineEmitter.off('line', onLine);
|
||||
this.#browserProcess.off('exit', onClose);
|
||||
this.#browserProcess.off('error', onClose);
|
||||
};
|
||||
|
||||
function onTimeout(): void {
|
||||
cleanup();
|
||||
reject(
|
||||
new TimeoutError(
|
||||
`Timed out after ${timeout} ms while waiting for the WS endpoint URL to appear in stdout!`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
for (const line of this.#logs) {
|
||||
onLine(line);
|
||||
}
|
||||
|
||||
function onLine(line: string): void {
|
||||
const match = line.match(regex);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
cleanup();
|
||||
// The RegExp matches, so this will obviously exist.
|
||||
resolve(match[1]!);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const PROCESS_ERROR_EXPLANATION = `Puppeteer was unable to kill the process which ran the browser binary.
|
||||
This means that, on future Puppeteer launches, Puppeteer might not be able to launch the browser.
|
||||
Please check your open processes and ensure that the browser processes that Puppeteer launched have been killed.
|
||||
If you think this is a bug, please report it on the Puppeteer issue tracker.`;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function pidExists(pid: number): boolean {
|
||||
try {
|
||||
return process.kill(pid, 0);
|
||||
} catch (error) {
|
||||
if (isErrnoException(error)) {
|
||||
if (error.code && error.code === 'ESRCH') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface ErrorLike extends Error {
|
||||
name: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function isErrorLike(obj: unknown): obj is ErrorLike {
|
||||
return (
|
||||
typeof obj === 'object' && obj !== null && 'name' in obj && 'message' in obj
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function isErrnoException(obj: unknown): obj is NodeJS.ErrnoException {
|
||||
return (
|
||||
isErrorLike(obj) &&
|
||||
('errno' in obj || 'code' in obj || 'path' in obj || 'syscall' in obj)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class TimeoutError extends Error {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
this.name = this.constructor.name;
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
11
node_modules/@puppeteer/browsers/src/main-cli.ts
generated
vendored
Normal file
11
node_modules/@puppeteer/browsers/src/main-cli.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {CLI} from './CLI.js';
|
||||
|
||||
void new CLI().run(process.argv);
|
||||
58
node_modules/@puppeteer/browsers/src/main.ts
generated
vendored
Normal file
58
node_modules/@puppeteer/browsers/src/main.ts
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export type {
|
||||
LaunchOptions,
|
||||
ComputeExecutablePathOptions as Options,
|
||||
SystemOptions,
|
||||
} from './launch.js';
|
||||
export {
|
||||
launch,
|
||||
computeExecutablePath,
|
||||
computeSystemExecutablePath,
|
||||
TimeoutError,
|
||||
CDP_WEBSOCKET_ENDPOINT_REGEX,
|
||||
WEBDRIVER_BIDI_WEBSOCKET_ENDPOINT_REGEX,
|
||||
Process,
|
||||
} from './launch.js';
|
||||
export type {
|
||||
InstallOptions,
|
||||
GetInstalledBrowsersOptions,
|
||||
UninstallOptions,
|
||||
} from './install.js';
|
||||
export {
|
||||
install,
|
||||
makeProgressCallback,
|
||||
getInstalledBrowsers,
|
||||
canDownload,
|
||||
uninstall,
|
||||
getDownloadUrl,
|
||||
} from './install.js';
|
||||
export {detectBrowserPlatform} from './detectPlatform.js';
|
||||
export type {ProfileOptions} from './browser-data/browser-data.js';
|
||||
export {
|
||||
resolveBuildId,
|
||||
Browser,
|
||||
BrowserPlatform,
|
||||
ChromeReleaseChannel,
|
||||
createProfile,
|
||||
getVersionComparator,
|
||||
resolveDefaultUserDataDir,
|
||||
} from './browser-data/browser-data.js';
|
||||
export {CLI} from './CLI.js';
|
||||
export {
|
||||
Cache,
|
||||
InstalledBrowser,
|
||||
type Metadata,
|
||||
type ComputeExecutablePathOptions,
|
||||
} from './Cache.js';
|
||||
export {BrowserTag} from './browser-data/types.js';
|
||||
export {DefaultProvider} from './DefaultProvider.js';
|
||||
export {
|
||||
type BrowserProvider,
|
||||
buildArchiveFilename,
|
||||
type DownloadOptions,
|
||||
} from './provider.js';
|
||||
166
node_modules/@puppeteer/browsers/src/provider.ts
generated
vendored
Normal file
166
node_modules/@puppeteer/browsers/src/provider.ts
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {Browser, BrowserPlatform} from './browser-data/browser-data.js';
|
||||
|
||||
/**
|
||||
* Options passed to a provider.
|
||||
* @public
|
||||
*/
|
||||
export interface DownloadOptions {
|
||||
browser: Browser;
|
||||
platform: BrowserPlatform;
|
||||
buildId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for custom browser provider implementations.
|
||||
* Allows users to implement alternative download sources for browsers.
|
||||
*
|
||||
* ⚠️ **IMPORTANT**: Custom providers are NOT officially supported by
|
||||
* Puppeteer.
|
||||
*
|
||||
* By implementing this interface, you accept full responsibility for:
|
||||
*
|
||||
* - Ensuring downloaded binaries are compatible with Puppeteer's expectations
|
||||
* - Testing that browser launch and other features work with your binaries
|
||||
* - Maintaining compatibility when Puppeteer or your download source changes
|
||||
* - Version consistency across platforms if mixing sources
|
||||
*
|
||||
* Puppeteer only tests and guarantees Chrome for Testing binaries.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```typescript
|
||||
* class ElectronDownloader implements BrowserProvider {
|
||||
* supports(options: DownloadOptions): boolean {
|
||||
* return options.browser === Browser.CHROMEDRIVER;
|
||||
* }
|
||||
*
|
||||
* getDownloadUrl(options: DownloadOptions): URL {
|
||||
* const platform = mapToPlatform(options.platform);
|
||||
* return new URL(
|
||||
* `v${options.buildId}/chromedriver-v${options.buildId}-${platform}.zip`,
|
||||
* 'https://github.com/electron/electron/releases/download/',
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* getExecutablePath(options): string {
|
||||
* const ext = options.platform.includes('win') ? '.exe' : '';
|
||||
* return `chromedriver/chromedriver${ext}`;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface BrowserProvider {
|
||||
/**
|
||||
* Check if this provider supports the given browser/platform.
|
||||
* Used for filtering before attempting downloads.
|
||||
*
|
||||
* Can be synchronous for quick checks or asynchronous if version
|
||||
* resolution/network requests are needed.
|
||||
*
|
||||
* @param options - Download options to check
|
||||
* @returns True if this provider supports the browser/platform combination
|
||||
*/
|
||||
supports(options: DownloadOptions): Promise<boolean> | boolean;
|
||||
|
||||
/**
|
||||
* Get the download URL for the requested browser.
|
||||
*
|
||||
* The buildId can be either an exact version (e.g., "131.0.6778.109")
|
||||
* or an alias (e.g., "latest", "stable"). Custom providers should handle
|
||||
* version resolution internally if they support aliases.
|
||||
*
|
||||
* Returns null if the buildId cannot be resolved to a valid version.
|
||||
* The URL is not validated - download will fail later if URL doesn't exist.
|
||||
*
|
||||
* Can be synchronous for simple URL construction or asynchronous if version
|
||||
* resolution/network requests are needed.
|
||||
*
|
||||
* @param options - Download options (buildId may be alias or exact version)
|
||||
* @returns Download URL, or null if version cannot be resolved
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Synchronous example
|
||||
* getDownloadUrl(options) {
|
||||
* const platform = mapPlatform(options.platform);
|
||||
* return new URL(`https://releases.example.com/v${options.buildId}/${platform}.zip`);
|
||||
* }
|
||||
*
|
||||
* // Asynchronous example with version mapping
|
||||
* async getDownloadUrl(options) {
|
||||
* const electronVersion = await resolveElectronVersion(options.buildId);
|
||||
* if (!electronVersion) return null;
|
||||
*
|
||||
* const platform = mapPlatform(options.platform);
|
||||
* return new URL(`https://github.com/electron/electron/releases/download/v${electronVersion}/${platform}.zip`);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getDownloadUrl(options: DownloadOptions): Promise<URL | null> | URL | null;
|
||||
|
||||
/**
|
||||
* Get the relative path to the executable within the extracted archive.
|
||||
*
|
||||
* @param options - Browser, buildId, and platform
|
||||
* @returns Relative path to the executable
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* // Electron uses simple structure
|
||||
* getExecutablePath() {
|
||||
* return 'chromedriver/chromedriver';
|
||||
* }
|
||||
*
|
||||
* // Custom provider with platform-specific paths
|
||||
* getExecutablePath(options) {
|
||||
* return `binaries/${options.browser}-${options.platform}`;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getExecutablePath(options: {
|
||||
browser: Browser;
|
||||
buildId: string;
|
||||
platform: BrowserPlatform;
|
||||
}): Promise<string> | string;
|
||||
|
||||
/**
|
||||
* Get the name of this provider.
|
||||
* Used for error messages and logging purposes.
|
||||
*
|
||||
* @returns The provider name (e.g., "DefaultProvider", "CustomProvider")
|
||||
*
|
||||
* @remarks
|
||||
* This method is used instead of `constructor.name` to avoid issues with
|
||||
* minification in production builds.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* getName() {
|
||||
* return 'MyCustomProvider';
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getName(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to build a standard archive filename.
|
||||
* @public
|
||||
*/
|
||||
export function buildArchiveFilename(
|
||||
browser: Browser,
|
||||
platform: BrowserPlatform,
|
||||
buildId: string,
|
||||
extension = 'zip',
|
||||
): string {
|
||||
return `${browser}-${platform}-${buildId}.${extension}`;
|
||||
}
|
||||
8
node_modules/@puppeteer/browsers/src/tsconfig.cjs.json
generated
vendored
Normal file
8
node_modules/@puppeteer/browsers/src/tsconfig.cjs.json
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "../lib/cjs"
|
||||
}
|
||||
}
|
||||
6
node_modules/@puppeteer/browsers/src/tsconfig.esm.json
generated
vendored
Normal file
6
node_modules/@puppeteer/browsers/src/tsconfig.esm.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../lib/esm"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user