add initial marp implementation with sample content and build configuration
This commit is contained in:
554
node_modules/@puppeteer/browsers/src/CLI.ts
generated
vendored
Normal file
554
node_modules/@puppeteer/browsers/src/CLI.ts
generated
vendored
Normal file
@@ -0,0 +1,554 @@
|
||||
/**
|
||||
* @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 {packageVersion} from './generated/version.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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
})}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
277
node_modules/@puppeteer/browsers/src/Cache.ts
generated
vendored
Normal file
277
node_modules/@puppeteer/browsers/src/Cache.ts
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* @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>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
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,
|
||||
);
|
||||
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};
|
||||
}
|
||||
252
node_modules/@puppeteer/browsers/src/browser-data/browser-data.ts
generated
vendored
Normal file
252
node_modules/@puppeteer/browsers/src/browser-data/browser-data.ts
generated
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
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.resolveSystemExecutablePath(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';
|
||||
215
node_modules/@puppeteer/browsers/src/browser-data/chrome.ts
generated
vendored
Normal file
215
node_modules/@puppeteer/browsers/src/browser-data/chrome.ts
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLastKnownGoodReleaseForChannel(
|
||||
channel: ChromeReleaseChannel,
|
||||
): Promise<{version: string; revision: string}> {
|
||||
const data = (await getJSON(
|
||||
new URL(
|
||||
'https://googlechromelabs.github.io/chrome-for-testing/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(
|
||||
'https://googlechromelabs.github.io/chrome-for-testing/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(
|
||||
'https://googlechromelabs.github.io/chrome-for-testing/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;
|
||||
}
|
||||
|
||||
export function resolveSystemExecutablePath(
|
||||
platform: BrowserPlatform,
|
||||
channel: ChromeReleaseChannel,
|
||||
): string {
|
||||
switch (platform) {
|
||||
case BrowserPlatform.WIN64:
|
||||
case BrowserPlatform.WIN32:
|
||||
switch (channel) {
|
||||
case ChromeReleaseChannel.STABLE:
|
||||
return `${process.env['PROGRAMFILES']}\\Google\\Chrome\\Application\\chrome.exe`;
|
||||
case ChromeReleaseChannel.BETA:
|
||||
return `${process.env['PROGRAMFILES']}\\Google\\Chrome Beta\\Application\\chrome.exe`;
|
||||
case ChromeReleaseChannel.CANARY:
|
||||
return `${process.env['PROGRAMFILES']}\\Google\\Chrome SxS\\Application\\chrome.exe`;
|
||||
case ChromeReleaseChannel.DEV:
|
||||
return `${process.env['PROGRAMFILES']}\\Google\\Chrome Dev\\Application\\chrome.exe`;
|
||||
}
|
||||
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:
|
||||
switch (channel) {
|
||||
case ChromeReleaseChannel.STABLE:
|
||||
return '/opt/google/chrome/chrome';
|
||||
case ChromeReleaseChannel.BETA:
|
||||
return '/opt/google/chrome-beta/chrome';
|
||||
case ChromeReleaseChannel.CANARY:
|
||||
return '/opt/google/chrome-canary/chrome';
|
||||
case ChromeReleaseChannel.DEV:
|
||||
return '/opt/google/chrome-unstable/chrome';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
455
node_modules/@puppeteer/browsers/src/browser-data/firefox.ts
generated
vendored
Normal file
455
node_modules/@puppeteer/browsers/src/browser-data/firefox.ts
generated
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
/**
|
||||
* @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',
|
||||
}
|
||||
|
||||
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('https://product-details.mozilla.org/1.0/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,
|
||||
|
||||
// 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']);
|
||||
}
|
||||
}
|
||||
4
node_modules/@puppeteer/browsers/src/generated/version.ts
generated
vendored
Normal file
4
node_modules/@puppeteer/browsers/src/generated/version.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const packageVersion = '2.10.9';
|
||||
162
node_modules/@puppeteer/browsers/src/httpUtil.ts
generated
vendored
Normal file
162
node_modules/@puppeteer/browsers/src/httpUtil.ts
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* @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('Chrome version not found'));
|
||||
}
|
||||
});
|
||||
},
|
||||
false,
|
||||
);
|
||||
request.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
522
node_modules/@puppeteer/browsers/src/install.ts
generated
vendored
Normal file
522
node_modules/@puppeteer/browsers/src/install.ts
generated
vendored
Normal file
@@ -0,0 +1,522 @@
|
||||
/**
|
||||
* @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 {detectBrowserPlatform} from './detectPlatform.js';
|
||||
import {unpackArchive} from './fileUtil.js';
|
||||
import {downloadFile, getJSON, headHttpRequest} from './httpUtil.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()})`,
|
||||
);
|
||||
}
|
||||
const url = getDownloadUrl(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.buildId,
|
||||
options.baseUrl,
|
||||
);
|
||||
try {
|
||||
return await installUrl(url, options);
|
||||
} catch (err) {
|
||||
// If custom baseUrl is provided, do not fall back to CfT dashboard.
|
||||
if (options.baseUrl && !options.forceFallbackForTesting) {
|
||||
throw err;
|
||||
}
|
||||
debugInstall(`Error downloading from ${url}.`);
|
||||
switch (options.browser) {
|
||||
case Browser.CHROME:
|
||||
case Browser.CHROMEDRIVER:
|
||||
case Browser.CHROMEHEADLESSSHELL: {
|
||||
debugInstall(
|
||||
`Trying to find download URL via https://googlechromelabs.github.io/chrome-for-testing.`,
|
||||
);
|
||||
interface Version {
|
||||
downloads: Record<string, Array<{platform: string; url: string}>>;
|
||||
}
|
||||
const version = (await getJSON(
|
||||
new URL(
|
||||
`https://googlechromelabs.github.io/chrome-for-testing/${options.buildId}.json`,
|
||||
),
|
||||
)) as Version;
|
||||
let platform = '';
|
||||
switch (options.platform) {
|
||||
case BrowserPlatform.LINUX:
|
||||
platform = 'linux64';
|
||||
break;
|
||||
case BrowserPlatform.MAC_ARM:
|
||||
platform = 'mac-arm64';
|
||||
break;
|
||||
case BrowserPlatform.MAC:
|
||||
platform = 'mac-x64';
|
||||
break;
|
||||
case BrowserPlatform.WIN32:
|
||||
platform = 'win32';
|
||||
break;
|
||||
case BrowserPlatform.WIN64:
|
||||
platform = 'win64';
|
||||
break;
|
||||
}
|
||||
const backupUrl = version.downloads[options.browser]?.find(link => {
|
||||
return link['platform'] === platform;
|
||||
})?.url;
|
||||
if (backupUrl) {
|
||||
// If the URL is the same, skip the retry.
|
||||
if (backupUrl === url.toString()) {
|
||||
throw err;
|
||||
}
|
||||
debugInstall(`Falling back to downloading from ${backupUrl}.`);
|
||||
return await installUrl(new URL(backupUrl), options);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
): Promise<InstalledBrowser | string> {
|
||||
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,
|
||||
);
|
||||
|
||||
try {
|
||||
if (existsSync(outputPath)) {
|
||||
const installedBrowser = new InstalledBrowser(
|
||||
cache,
|
||||
options.browser,
|
||||
options.buildId,
|
||||
options.platform,
|
||||
);
|
||||
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;
|
||||
}
|
||||
debugInstall(`Downloading binary from ${url}`);
|
||||
try {
|
||||
debugTime('download');
|
||||
await downloadFile(url, archivePath, downloadProgressCallback);
|
||||
} finally {
|
||||
debugTimeEnd('download');
|
||||
}
|
||||
|
||||
debugInstall(`Installing ${archivePath} to ${outputPath}`);
|
||||
try {
|
||||
debugTime('extract');
|
||||
await unpackArchive(archivePath, outputPath);
|
||||
} finally {
|
||||
debugTimeEnd('extract');
|
||||
}
|
||||
|
||||
const installedBrowser = new InstalledBrowser(
|
||||
cache,
|
||||
options.browser,
|
||||
options.buildId,
|
||||
options.platform,
|
||||
);
|
||||
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()})`,
|
||||
);
|
||||
}
|
||||
return await headHttpRequest(
|
||||
getDownloadUrl(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.buildId,
|
||||
options.baseUrl,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`;
|
||||
}
|
||||
603
node_modules/@puppeteer/browsers/src/launch.ts
generated
vendored
Normal file
603
node_modules/@puppeteer/browsers/src/launch.ts
generated
vendored
Normal file
@@ -0,0 +1,603 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import childProcess from 'node:child_process';
|
||||
import {accessSync} from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import readline from 'node:readline';
|
||||
|
||||
import {
|
||||
type Browser,
|
||||
type BrowserPlatform,
|
||||
resolveSystemExecutablePath,
|
||||
type ChromeReleaseChannel,
|
||||
executablePathByBrowser,
|
||||
} 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
|
||||
* 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 path = resolveSystemExecutablePath(
|
||||
options.browser,
|
||||
options.platform,
|
||||
options.channel,
|
||||
);
|
||||
try {
|
||||
accessSync(path);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Could not find Google Chrome executable for channel '${options.channel}' at '${path}'.`,
|
||||
);
|
||||
}
|
||||
return 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>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>;
|
||||
|
||||
constructor(opts: LaunchOptions) {
|
||||
this.#executablePath = opts.executablePath;
|
||||
this.#args = opts.args ?? [];
|
||||
|
||||
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,
|
||||
dumpio: opts.dumpio,
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
);
|
||||
|
||||
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;
|
||||
dumpio: boolean;
|
||||
}): Array<'ignore' | 'pipe'> {
|
||||
if (opts.pipe) {
|
||||
if (opts.dumpio) {
|
||||
return ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'];
|
||||
} else {
|
||||
return ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'];
|
||||
}
|
||||
} else {
|
||||
if (opts.dumpio) {
|
||||
return ['pipe', 'pipe', 'pipe'];
|
||||
} else {
|
||||
return ['pipe', 'ignore', 'pipe'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#clearListeners(): void {
|
||||
unsubscribeFromProcessEvent('exit', this.#onDriverProcessExit);
|
||||
unsubscribeFromProcessEvent('SIGINT', this.#onDriverProcessSignal);
|
||||
unsubscribeFromProcessEvent('SIGTERM', this.#onDriverProcessSignal);
|
||||
unsubscribeFromProcessEvent('SIGHUP', this.#onDriverProcessSignal);
|
||||
}
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
waitForLineOutput(regex: RegExp, timeout = 0): Promise<string> {
|
||||
if (!this.#browserProcess.stderr) {
|
||||
throw new Error('`browserProcess` does not have stderr.');
|
||||
}
|
||||
const rl = readline.createInterface(this.#browserProcess.stderr);
|
||||
let stderr = '';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
rl.on('line', onLine);
|
||||
rl.on('close', onClose);
|
||||
this.#browserProcess.on('exit', onClose);
|
||||
this.#browserProcess.on('error', onClose);
|
||||
const timeoutId =
|
||||
timeout > 0 ? setTimeout(onTimeout, timeout) : undefined;
|
||||
|
||||
const cleanup = (): void => {
|
||||
clearTimeout(timeoutId);
|
||||
rl.off('line', onLine);
|
||||
rl.off('close', onClose);
|
||||
rl.close();
|
||||
this.#browserProcess.off('exit', onClose);
|
||||
this.#browserProcess.off('error', onClose);
|
||||
};
|
||||
|
||||
function onClose(error?: Error): void {
|
||||
cleanup();
|
||||
reject(
|
||||
new Error(
|
||||
[
|
||||
`Failed to launch the browser process!${
|
||||
error ? ' ' + error.message : ''
|
||||
}`,
|
||||
stderr,
|
||||
'',
|
||||
'TROUBLESHOOTING: https://pptr.dev/troubleshooting',
|
||||
'',
|
||||
].join('\n'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function onTimeout(): void {
|
||||
cleanup();
|
||||
reject(
|
||||
new TimeoutError(
|
||||
`Timed out after ${timeout} ms while waiting for the WS endpoint URL to appear in stdout!`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function onLine(line: string): void {
|
||||
stderr += line + '\n';
|
||||
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);
|
||||
51
node_modules/@puppeteer/browsers/src/main.ts
generated
vendored
Normal file
51
node_modules/@puppeteer/browsers/src/main.ts
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @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,
|
||||
} 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';
|
||||
4
node_modules/@puppeteer/browsers/src/templates/version.ts.tmpl
generated
vendored
Normal file
4
node_modules/@puppeteer/browsers/src/templates/version.ts.tmpl
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const packageVersion = 'PACKAGE_VERSION';
|
||||
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