chore: add a basic CLI for fetching the browsers (#9671)

This commit is contained in:
Alex Rudenko 2023-02-14 16:30:41 +01:00 committed by GitHub
parent c6054ac1a5
commit 6e428edb9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 227 additions and 20 deletions

57
package-lock.json generated
View File

@ -2708,7 +2708,6 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
@ -8426,10 +8425,9 @@
}
},
"node_modules/yargs": {
"version": "17.6.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz",
"integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==",
"dev": true,
"version": "17.6.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
"integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
@ -8437,7 +8435,7 @@
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.0.0"
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
@ -8500,7 +8498,6 @@
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
"node": ">=12"
}
@ -8571,12 +8568,18 @@
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3"
"unbzip2-stream": "1.4.3",
"yargs": "17.6.2"
},
"bin": {
"browsers": "lib/cjs/browsers.js"
},
"devDependencies": {
"@types/node": "^14.15.0"
"@types/node": "^14.15.0",
"@types/yargs": "17.0.22"
},
"engines": {
"node": ">=14.1.0"
@ -8596,6 +8599,15 @@
"integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==",
"dev": true
},
"packages/browsers/node_modules/@types/yargs": {
"version": "17.0.22",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz",
"integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==",
"dev": true,
"dependencies": {
"@types/yargs-parser": "*"
}
},
"packages/ng-schematics": {
"name": "@puppeteer/ng-schematics",
"version": "0.1.0",
@ -9965,12 +9977,15 @@
"version": "file:packages/browsers",
"requires": {
"@types/node": "^14.15.0",
"@types/yargs": "17.0.22",
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3"
"unbzip2-stream": "1.4.3",
"yargs": "17.6.2"
},
"dependencies": {
"@types/node": {
@ -9978,6 +9993,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.36.tgz",
"integrity": "sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==",
"dev": true
},
"@types/yargs": {
"version": "17.0.22",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz",
"integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
}
}
}
},
@ -11023,7 +11047,6 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
@ -15216,10 +15239,9 @@
"dev": true
},
"yargs": {
"version": "17.6.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz",
"integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==",
"dev": true,
"version": "17.6.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
"integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==",
"requires": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
@ -15227,14 +15249,13 @@
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.0.0"
"yargs-parser": "^21.1.1"
},
"dependencies": {
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
}
}
},

View File

@ -40,6 +40,7 @@
"wireit": {
"build": {
"dependencies": [
"./packages/browsers:build",
"./packages/ng-schematics:build",
"./packages/puppeteer-core:build",
"./packages/puppeteer:build",

View File

@ -8,6 +8,9 @@
"clean": "tsc --build --clean && rimraf lib",
"test": "wireit"
},
"bin": {
"@puppeteer/browsers": "lib/cjs/browsers.js"
},
"wireit": {
"build": {
"command": "tsc -b",
@ -63,12 +66,15 @@
"debug": "4.3.4",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3"
"unbzip2-stream": "1.4.3",
"yargs": "17.6.2"
},
"devDependencies": {
"@types/node": "^14.15.0"
"@types/node": "^14.15.0",
"@types/yargs": "17.0.22"
},
"peerDependencies": {
"typescript": ">= 4.7.4"

View File

@ -0,0 +1,107 @@
import yargs from 'yargs';
import ProgressBar from 'progress';
import {hideBin} from 'yargs/helpers';
import {Browser, BrowserPlatform} from './browsers/types.js';
import {fetch} from './fetch.js';
import path from 'path';
type Arguments = {
browser: {
name: Browser;
revision: string;
};
path?: string;
platform?: BrowserPlatform;
};
export class CLI {
#cachePath;
constructor(cachePath = process.cwd()) {
this.#cachePath = cachePath;
}
async run(argv: string[]): Promise<void> {
await yargs(hideBin(argv))
.command(
'$0 install <browser>',
'run files',
yargs => {
yargs.positional('browser', {
description: 'The browser version',
type: 'string',
coerce: (opt): Arguments['browser'] => {
return {
name: this.#parseBrowser(opt),
revision: this.#parseRevision(opt),
};
},
});
},
async argv => {
const args = argv as unknown as Arguments;
await fetch({
browser: args.browser.name,
revision: args.browser.revision,
platform: args.platform,
outputDir: path.join(
args.path ?? this.#cachePath,
args.browser.name
),
progressCallback: this.#makeProgressBar(
args.browser.name,
args.browser.revision
),
});
}
)
.option('path', {
type: 'string',
desc: 'Path where the browsers will be downloaded to and installed from',
default: process.cwd(),
})
.option('platform', {
type: 'string',
desc: 'Platform that the binary needs to be compatible with.',
choices: Object.values(BrowserPlatform),
defaultDescription: 'Auto-detected by default.',
})
.parse();
}
#parseBrowser(version: string): Browser {
return version.split('@').shift() as Browser;
}
#parseRevision(version: string): string {
return version.split('@').pop() ?? 'latest';
}
#toMegabytes(bytes: number) {
const mb = bytes / 1024 / 1024;
return `${Math.round(mb * 10) / 10} Mb`;
}
#makeProgressBar(browser: Browser, revision: string) {
let progressBar: ProgressBar | null = null;
let lastDownloadedBytes = 0;
return (downloadedBytes: number, totalBytes: number) => {
if (!progressBar) {
progressBar = new ProgressBar(
`Downloading ${browser} r${revision} - ${this.#toMegabytes(
totalBytes
)} [:bar] :percent :etas `,
{
complete: '=',
incomplete: ' ',
width: 20,
total: totalBytes,
}
);
}
const delta = downloadedBytes - lastDownloadedBytes;
lastDownloadedBytes = downloadedBytes;
progressBar.tick(delta);
};
}
}

View File

@ -0,0 +1,5 @@
#!/usr/bin/env node
import {CLI} from './CLI.js';
new CLI().run(process.argv);

View File

@ -0,0 +1,67 @@
/**
* 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.
*/
import {CLI} from '../../lib/cjs/CLI.js';
import fs from 'fs';
import path from 'path';
import os from 'os';
import assert from 'assert';
describe('CLI', function () {
this.timeout(60000);
let tmpDir = '/tmp/puppeteer-browsers-test';
const testChromeRevision = '1083080';
const testFirefoxRevision = '111.0a1';
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test'));
});
afterEach(() => {
fs.rmSync(tmpDir, {recursive: true});
});
it('should download Chromium binaries', async () => {
await new CLI(tmpDir).run([
'npx',
'@puppeteer/browsers',
'install',
`chrome@${testChromeRevision}`,
`--path=${tmpDir}`,
'--platform=linux',
]);
assert.ok(
fs.existsSync(path.join(tmpDir, 'chrome', `linux-${testChromeRevision}`))
);
});
it('should download Firefox binaries', async () => {
await new CLI(tmpDir).run([
'npx',
'@puppeteer/browsers',
'install',
`firefox@${testFirefoxRevision}`,
`--path=${tmpDir}`,
'--platform=linux',
]);
assert.ok(
fs.existsSync(
path.join(tmpDir, 'firefox', `linux-${testFirefoxRevision}`)
)
);
});
});