feat: implement a command to clear the cache (#9868)

This commit is contained in:
Alex Rudenko 2023-03-16 14:05:31 +01:00 committed by GitHub
parent 21a082fb9f
commit b8d38cb05f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 109 additions and 22 deletions

4
package-lock.json generated
View File

@ -9123,7 +9123,7 @@
},
"packages/browsers": {
"name": "@puppeteer/browsers",
"version": "0.0.5",
"version": "0.1.1",
"license": "Apache-2.0",
"dependencies": {
"debug": "4.3.4",
@ -9137,7 +9137,7 @@
"yargs": "17.7.1"
},
"bin": {
"browsers": "lib/cjs/browsers.js"
"browsers": "lib/cjs/main-cli.js"
},
"devDependencies": {
"@types/node": "^14.15.0",

View File

@ -14,6 +14,9 @@
* limitations under the License.
*/
import {stdin as input, stdout as output} from 'process';
import * as readline from 'readline';
import ProgressBar from 'progress';
import yargs from 'yargs';
import {hideBin} from 'yargs/helpers';
@ -24,6 +27,7 @@ import {
BrowserPlatform,
ChromeReleaseChannel,
} from './browser-data/browser-data.js';
import {Cache} from './Cache.js';
import {detectBrowserPlatform} from './detectPlatform.js';
import {fetch} from './fetch.js';
import {
@ -52,11 +56,17 @@ type LaunchArgs = {
system: boolean;
};
type ClearArgs = {
path?: string;
};
export class CLI {
#cachePath;
#rl?: readline.Interface;
constructor(cachePath = process.cwd()) {
constructor(cachePath = process.cwd(), rl?: readline.Interface) {
this.#cachePath = cachePath;
this.#rl = rl;
}
#defineBrowserParameter(yargs: yargs.Argv<unknown>): void {
@ -82,13 +92,16 @@ export class CLI {
});
}
#definePathParameter(yargs: yargs.Argv<unknown>): void {
#definePathParameter(yargs: yargs.Argv<unknown>, required = false): void {
yargs.option('path', {
type: 'string',
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.',
default: process.cwd(),
defaultDescription: 'Current working directory',
...(required ? {} : {default: process.cwd()}),
});
if (required) {
yargs.demandOption('path');
}
}
async run(argv: string[]): Promise<void> {
@ -214,6 +227,31 @@ export class CLI {
});
}
)
.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.`);
}
);
}
)
.demandCommand(1)
.help()
.wrap(Math.min(120, yargs.terminalWidth()))

View File

@ -16,6 +16,8 @@
import path from 'path';
import rimraf from 'rimraf';
import {Browser, BrowserPlatform} from './browser-data/browser-data.js';
/**
@ -32,7 +34,7 @@ import {Browser, BrowserPlatform} from './browser-data/browser-data.js';
* ------ specific structure.
* @internal
*/
export class CacheStructure {
export class Cache {
#rootDir: string;
constructor(rootDir: string) {
@ -50,4 +52,8 @@ export class CacheStructure {
): string {
return path.join(this.browserRoot(browser), `${platform}-${buildId}`);
}
clear(): void {
rimraf.sync(this.#rootDir);
}
}

View File

@ -25,7 +25,7 @@ import {
BrowserPlatform,
downloadUrls,
} from './browser-data/browser-data.js';
import {CacheStructure} from './CacheStructure.js';
import {Cache} from './Cache.js';
import {debug} from './debug.js';
import {detectBrowserPlatform} from './detectPlatform.js';
import {unpackArchive} from './fileUtil.js';
@ -86,7 +86,7 @@ export async function fetch(options: Options): Promise<InstalledBrowser> {
);
const fileName = url.toString().split('/').pop();
assert(fileName, `A malformed download URL was found: ${url}.`);
const structure = new CacheStructure(options.cacheDir);
const structure = new Cache(options.cacheDir);
const browserRoot = structure.browserRoot(options.browser);
const archivePath = path.join(browserRoot, fileName);
if (!existsSync(browserRoot)) {

View File

@ -27,7 +27,7 @@ import {
resolveSystemExecutablePath,
ChromeReleaseChannel,
} from './browser-data/browser-data.js';
import {CacheStructure} from './CacheStructure.js';
import {Cache} from './Cache.js';
import {debug} from './debug.js';
import {detectBrowserPlatform} from './detectPlatform.js';
@ -65,7 +65,7 @@ export function computeExecutablePath(options: Options): string {
`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`
);
}
const installationDir = new CacheStructure(options.cacheDir).installationDir(
const installationDir = new Cache(options.cacheDir).installationDir(
options.browser,
options.platform,
options.buildId

View File

@ -29,3 +29,4 @@ export {
ChromeReleaseChannel,
} from './browser-data/browser-data.js';
export {CLI} from './CLI.js';
export {Cache} from './Cache.js';

View File

@ -18,10 +18,11 @@ import assert from 'assert';
import fs from 'fs';
import os from 'os';
import path from 'path';
import rimraf from 'rimraf';
import * as readline from 'readline';
import {Writable, Readable} from 'stream';
import {CLI} from '../../lib/cjs/CLI.js';
import {Cache} from '../../lib/cjs/main.js';
import {testChromeBuildId, testFirefoxBuildId} from './versions.js';
@ -30,12 +31,33 @@ describe('CLI', function () {
let tmpDir = '/tmp/puppeteer-browsers-test';
function createMockedInterface(input: string) {
const readable = Readable.from([input]);
const writable = new Writable({
write(_chunk, _encoding, callback) {
// Suppress the output to keep the test clean
callback();
},
});
return readline.createInterface({
input: readable,
output: writable,
});
}
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'puppeteer-browsers-test'));
});
afterEach(() => {
rimraf.sync(tmpDir);
afterEach(async () => {
new Cache(tmpDir).clear();
await new CLI(tmpDir, createMockedInterface('yes')).run([
'npx',
'@puppeteer/browsers',
'clear',
`--path=${tmpDir}`,
]);
});
it('should download Chromium binaries', async () => {
@ -57,6 +79,23 @@ describe('CLI', function () {
)
)
);
await new CLI(tmpDir, createMockedInterface('no')).run([
'npx',
'@puppeteer/browsers',
'clear',
`--path=${tmpDir}`,
]);
assert.ok(
fs.existsSync(
path.join(
tmpDir,
'chrome',
`linux-${testChromeBuildId}`,
'chrome-linux'
)
)
);
});
it('should download Firefox binaries', async () => {

View File

@ -21,9 +21,13 @@ import https from 'https';
import os from 'os';
import path from 'path';
import rimraf from 'rimraf';
import {fetch, canFetch, Browser, BrowserPlatform} from '../../lib/cjs/main.js';
import {
fetch,
canFetch,
Browser,
BrowserPlatform,
Cache,
} from '../../lib/cjs/main.js';
import {testChromeBuildId, testFirefoxBuildId} from './versions.js';
@ -39,7 +43,7 @@ describe('fetch', () => {
});
afterEach(() => {
rimraf.sync(tmpDir);
new Cache(tmpDir).clear();
});
it('should check if a buildId can be downloaded', async () => {

View File

@ -19,8 +19,6 @@ import fs from 'fs';
import os from 'os';
import path from 'path';
import rimraf from 'rimraf';
import {
CDP_WEBSOCKET_ENDPOINT_REGEX,
computeExecutablePath,
@ -28,6 +26,7 @@ import {
fetch,
Browser,
BrowserPlatform,
Cache,
} from '../../lib/cjs/main.js';
import {testChromeBuildId, testFirefoxBuildId} from './versions.js';
@ -86,7 +85,7 @@ describe('launcher', () => {
});
afterEach(() => {
rimraf.sync(tmpDir);
new Cache(tmpDir).clear();
});
it('should launch a Chrome browser', async () => {
@ -146,7 +145,7 @@ describe('launcher', () => {
});
afterEach(() => {
rimraf.sync(tmpDir);
new Cache(tmpDir).clear();
});
it('should launch a Firefox browser', async () => {