2023-02-15 19:41:22 +00:00
/ * *
* Copyright 2023 Google Inc . All rights reserved .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2023-03-16 13:05:31 +00:00
import { stdin as input , stdout as output } from 'process' ;
import * as readline from 'readline' ;
2023-02-14 15:30:41 +00:00
import ProgressBar from 'progress' ;
2023-04-04 11:58:32 +00:00
import type * as Yargs from 'yargs' ;
2023-02-14 15:30:41 +00:00
import { hideBin } from 'yargs/helpers' ;
2023-04-04 11:58:32 +00:00
import yargs from 'yargs/yargs' ;
2023-02-15 23:09:31 +00:00
2023-03-14 11:01:11 +00:00
import {
2023-03-16 07:16:44 +00:00
resolveBuildId ,
2023-03-14 11:01:11 +00:00
Browser ,
BrowserPlatform ,
ChromeReleaseChannel ,
2023-03-16 07:16:44 +00:00
} from './browser-data/browser-data.js' ;
2023-03-16 13:05:31 +00:00
import { Cache } from './Cache.js' ;
2023-02-21 14:19:06 +00:00
import { detectBrowserPlatform } from './detectPlatform.js' ;
2023-02-14 15:30:41 +00:00
import { fetch } from './fetch.js' ;
2023-03-14 11:01:11 +00:00
import {
computeExecutablePath ,
computeSystemExecutablePath ,
launch ,
} from './launcher.js' ;
2023-02-14 15:30:41 +00:00
2023-02-17 06:11:50 +00:00
type InstallArgs = {
2023-02-14 15:30:41 +00:00
browser : {
name : Browser ;
2023-02-21 16:15:49 +00:00
buildId : string ;
2023-02-14 15:30:41 +00:00
} ;
path? : string ;
platform? : BrowserPlatform ;
} ;
2023-02-17 06:11:50 +00:00
type LaunchArgs = {
browser : {
name : Browser ;
2023-02-21 16:15:49 +00:00
buildId : string ;
2023-02-17 06:11:50 +00:00
} ;
path? : string ;
platform? : BrowserPlatform ;
detached : boolean ;
2023-03-14 11:01:11 +00:00
system : boolean ;
2023-02-17 06:11:50 +00:00
} ;
2023-03-16 13:05:31 +00:00
type ClearArgs = {
path? : string ;
} ;
2023-02-14 15:30:41 +00:00
export class CLI {
# cachePath ;
2023-03-16 13:05:31 +00:00
# rl? : readline.Interface ;
2023-02-14 15:30:41 +00:00
2023-03-16 13:05:31 +00:00
constructor ( cachePath = process . cwd ( ) , rl? : readline.Interface ) {
2023-02-14 15:30:41 +00:00
this . # cachePath = cachePath ;
2023-03-16 13:05:31 +00:00
this . # rl = rl ;
2023-02-14 15:30:41 +00:00
}
2023-04-04 11:58:32 +00:00
# defineBrowserParameter ( yargs : Yargs.Argv < unknown > ) : void {
2023-03-08 12:36:31 +00:00
yargs . positional ( 'browser' , {
2023-03-16 08:57:28 +00:00
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.' ,
2023-03-08 12:36:31 +00:00
type : 'string' ,
coerce : ( opt ) : InstallArgs [ 'browser' ] = > {
return {
name : this. # parseBrowser ( opt ) ,
buildId : this. # parseBuildId ( opt ) ,
} ;
} ,
} ) ;
}
2023-04-04 11:58:32 +00:00
# definePlatformParameter ( yargs : Yargs.Argv < unknown > ) : void {
2023-03-08 12:36:31 +00:00
yargs . option ( 'platform' , {
type : 'string' ,
desc : 'Platform that the binary needs to be compatible with.' ,
choices : Object.values ( BrowserPlatform ) ,
2023-03-16 08:57:28 +00:00
defaultDescription : 'Auto-detected' ,
2023-03-08 12:36:31 +00:00
} ) ;
}
2023-04-04 11:58:32 +00:00
# definePathParameter ( yargs : Yargs.Argv < unknown > , required = false ) : void {
2023-03-08 12:36:31 +00:00
yargs . option ( 'path' , {
type : 'string' ,
2023-03-16 08:57:28 +00:00
desc : 'Path to the root folder for the browser downloads and installation. The installation folder structure is compatible with the cache structure used by Puppeteer.' ,
defaultDescription : 'Current working directory' ,
2023-03-16 13:05:31 +00:00
. . . ( required ? { } : { default : process . cwd ( ) } ) ,
2023-03-08 12:36:31 +00:00
} ) ;
2023-03-16 13:05:31 +00:00
if ( required ) {
yargs . demandOption ( 'path' ) ;
}
2023-03-08 12:36:31 +00:00
}
2023-02-14 15:30:41 +00:00
async run ( argv : string [ ] ) : Promise < void > {
2023-04-04 11:58:32 +00:00
const yargsInstance = yargs ( hideBin ( argv ) ) ;
await yargsInstance
2023-03-16 08:57:28 +00:00
. scriptName ( '@puppeteer/browsers' )
2023-02-14 15:30:41 +00:00
. command (
2023-02-17 06:11:50 +00:00
'install <browser>' ,
2023-03-16 08:57:28 +00:00
'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>).' ,
2023-02-14 15:30:41 +00:00
yargs = > {
2023-03-08 12:36:31 +00:00
this . # defineBrowserParameter ( yargs ) ;
this . # definePlatformParameter ( yargs ) ;
this . # definePathParameter ( yargs ) ;
2023-03-16 08:57:28 +00:00
yargs . example (
'$0 install chrome' ,
'Install the latest available build of the Chrome browser.'
) ;
yargs . example (
'$0 install chrome@latest' ,
'Install the latest available build for the Chrome browser.'
) ;
yargs . example (
'$0 install chromium@1083080' ,
'Install the revision 1083080 of the Chromium browser.'
) ;
yargs . example (
'$0 install firefox' ,
'Install the latest available build of the Firefox browser.'
) ;
yargs . example (
'$0 install firefox --platform mac' ,
'Install the latest Mac (Intel) build of the Firefox browser.'
) ;
yargs . example (
'$0 install firefox --path /tmp/my-browser-cache' ,
'Install to the specified cache directory.'
) ;
2023-02-14 15:30:41 +00:00
} ,
async argv = > {
2023-02-17 06:11:50 +00:00
const args = argv as unknown as InstallArgs ;
2023-02-21 14:19:06 +00:00
args . platform ? ? = detectBrowserPlatform ( ) ;
if ( ! args . platform ) {
throw new Error ( ` Could not resolve the current platform ` ) ;
}
2023-02-21 16:15:49 +00:00
args . browser . buildId = await resolveBuildId (
2023-02-21 07:27:02 +00:00
args . browser . name ,
2023-02-21 14:19:06 +00:00
args . platform ,
2023-02-21 16:15:49 +00:00
args . browser . buildId
2023-02-21 07:27:02 +00:00
) ;
2023-02-14 15:30:41 +00:00
await fetch ( {
browser : args.browser.name ,
2023-02-21 16:15:49 +00:00
buildId : args.browser.buildId ,
2023-02-14 15:30:41 +00:00
platform : args.platform ,
2023-02-15 19:41:22 +00:00
cacheDir : args.path ? ? this . # cachePath ,
2023-03-22 09:04:41 +00:00
downloadProgressCallback : makeProgressCallback (
2023-02-14 15:30:41 +00:00
args . browser . name ,
2023-02-21 16:15:49 +00:00
args . browser . buildId
2023-02-14 15:30:41 +00:00
) ,
} ) ;
2023-02-21 07:27:02 +00:00
console . log (
2023-03-07 15:30:32 +00:00
` ${ args . browser . name } @ ${
args . browser . buildId
} $ { computeExecutablePath ( {
browser : args.browser.name ,
buildId : args.browser.buildId ,
cacheDir : args.path ? ? this . # cachePath ,
platform : args.platform ,
} ) } `
2023-02-21 07:27:02 +00:00
) ;
2023-02-14 15:30:41 +00:00
}
)
2023-02-17 06:11:50 +00:00
. command (
'launch <browser>' ,
'Launch the specified browser' ,
yargs = > {
2023-03-08 12:36:31 +00:00
this . # defineBrowserParameter ( yargs ) ;
this . # definePlatformParameter ( yargs ) ;
this . # definePathParameter ( yargs ) ;
2023-02-22 15:38:15 +00:00
yargs . option ( 'detached' , {
type : 'boolean' ,
2023-03-14 11:01:11 +00:00
desc : 'Detach the child process.' ,
default : false ,
} ) ;
yargs . option ( 'system' , {
type : 'boolean' ,
desc : 'Search for a browser installed on the system instead of the cache folder.' ,
2023-02-22 15:38:15 +00:00
default : false ,
} ) ;
2023-03-16 08:57:28 +00:00
yargs . example (
'$0 launch chrome@1083080' ,
'Launch the Chrome browser identified by the revision 1083080.'
) ;
yargs . example (
'$0 launch firefox@112.0a1' ,
'Launch the Firefox browser identified by the milestone 112.0a1.'
) ;
yargs . example (
'$0 launch chrome@1083080 --detached' ,
'Launch the browser but detach the sub-processes.'
) ;
yargs . example (
'$0 launch chrome@canary --system' ,
'Try to locate the Canary build of Chrome installed on the system and launch it.'
) ;
2023-02-17 06:11:50 +00:00
} ,
async argv = > {
const args = argv as unknown as LaunchArgs ;
2023-03-14 11:01:11 +00:00
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 ,
} ) ;
2023-02-17 06:11:50 +00:00
launch ( {
executablePath ,
detached : args.detached ,
} ) ;
}
)
2023-03-16 13:05:31 +00:00
. command (
'clear' ,
'Removes all installed browsers from the specified cache directory' ,
yargs = > {
this . # definePathParameter ( yargs , true ) ;
} ,
async argv = > {
const args = argv as unknown as ClearArgs ;
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. ` ) ;
}
) ;
}
)
2023-02-22 12:10:26 +00:00
. demandCommand ( 1 )
. help ( )
2023-04-04 11:58:32 +00:00
. wrap ( Math . min ( 120 , yargsInstance . terminalWidth ( ) ) )
2023-02-14 15:30:41 +00:00
. parse ( ) ;
}
# parseBrowser ( version : string ) : Browser {
return version . split ( '@' ) . shift ( ) as Browser ;
}
2023-02-21 16:15:49 +00:00
# parseBuildId ( version : string ) : string {
2023-02-14 15:30:41 +00:00
return version . split ( '@' ) . pop ( ) ? ? 'latest' ;
}
2023-03-22 09:04:41 +00:00
}
2023-02-14 15:30:41 +00:00
2023-03-22 09:04:41 +00:00
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 ProgressBar (
` Downloading ${ browser } r ${ buildId } - ${ toMegabytes (
totalBytes
) } [ : bar ] : percent :etas ` ,
{
complete : '=' ,
incomplete : ' ' ,
width : 20 ,
total : totalBytes ,
}
) ;
}
const delta = downloadedBytes - lastDownloadedBytes ;
lastDownloadedBytes = downloadedBytes ;
progressBar . tick ( delta ) ;
} ;
}
2023-02-14 15:30:41 +00:00
2023-03-22 09:04:41 +00:00
function toMegabytes ( bytes : number ) {
const mb = bytes / 1000 / 1000 ;
return ` ${ Math . round ( mb * 10 ) / 10 } MB ` ;
2023-02-14 15:30:41 +00:00
}