mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat: implement the Puppeteer CLI (#11344)
This commit is contained in:
parent
a7fcde9181
commit
53fb69bf7f
@ -10,13 +10,29 @@ Constructs a new instance of the `CLI` class
|
||||
|
||||
```typescript
|
||||
class CLI {
|
||||
constructor(cachePath?: string, rl?: readline.Interface);
|
||||
constructor(
|
||||
opts?:
|
||||
| string
|
||||
| {
|
||||
cachePath?: string;
|
||||
scriptName?: string;
|
||||
prefixCommand?: {
|
||||
cmd: string;
|
||||
description: string;
|
||||
};
|
||||
allowCachePathOverride?: boolean;
|
||||
pinnedBrowsers?: Partial<{
|
||||
[key in Browser]: string;
|
||||
}>;
|
||||
},
|
||||
rl?: readline.Interface
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------------------ | ------------ |
|
||||
| cachePath | string | _(Optional)_ |
|
||||
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
|
||||
| opts | string \| { cachePath?: string; scriptName?: string; prefixCommand?: { cmd: string; description: string; }; allowCachePathOverride?: boolean; pinnedBrowsers?: Partial<{ \[key in [Browser](./browsers.browser.md)\]: string; }>; } | _(Optional)_ |
|
||||
| rl | readline.Interface | _(Optional)_ |
|
||||
|
@ -13,8 +13,8 @@ export declare class CLI
|
||||
## Constructors
|
||||
|
||||
| Constructor | Modifiers | Description |
|
||||
| --------------------------------------------------------------- | --------- | ------------------------------------------------------- |
|
||||
| [(constructor)(cachePath, rl)](./browsers.cli._constructor_.md) | | Constructs a new instance of the <code>CLI</code> class |
|
||||
| ---------------------------------------------------------- | --------- | ------------------------------------------------------- |
|
||||
| [(constructor)(opts, rl)](./browsers.cli._constructor_.md) | | Constructs a new instance of the <code>CLI</code> class |
|
||||
|
||||
## Methods
|
||||
|
||||
|
@ -32,7 +32,7 @@ again.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# Or to download Firefox
|
||||
# Or to download Firefox by default
|
||||
PUPPETEER_PRODUCT=firefox npm install
|
||||
```
|
||||
|
||||
|
@ -117,6 +117,12 @@ To fetch Firefox Nightly as part of Puppeteer installation:
|
||||
PUPPETEER_PRODUCT=firefox npm i puppeteer
|
||||
```
|
||||
|
||||
To download Firefox Nightly into an existing Puppeteer project:
|
||||
|
||||
```bash
|
||||
npx puppeteer browsers install firefox
|
||||
```
|
||||
|
||||
#### Q: What’s considered a “Navigation”?
|
||||
|
||||
From Puppeteer’s standpoint, **“navigation” is anything that changes a page’s
|
||||
|
3
package-lock.json
generated
3
package-lock.json
generated
@ -11986,6 +11986,9 @@
|
||||
"cosmiconfig": "8.3.6",
|
||||
"puppeteer-core": "21.5.2"
|
||||
},
|
||||
"bin": {
|
||||
"puppeteer": "lib/esm/puppeteer/node/cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.17.15"
|
||||
},
|
||||
|
@ -68,10 +68,37 @@ interface ClearArgs {
|
||||
export class CLI {
|
||||
#cachePath;
|
||||
#rl?: readline.Interface;
|
||||
#scriptName = '';
|
||||
#allowCachePathOverride = true;
|
||||
#pinnedBrowsers?: Partial<{[key in Browser]: string}>;
|
||||
#prefixCommand?: {cmd: string; description: string};
|
||||
|
||||
constructor(cachePath = process.cwd(), rl?: readline.Interface) {
|
||||
this.#cachePath = cachePath;
|
||||
constructor(
|
||||
opts?:
|
||||
| string
|
||||
| {
|
||||
cachePath?: string;
|
||||
scriptName?: string;
|
||||
prefixCommand?: {cmd: string; description: string};
|
||||
allowCachePathOverride?: boolean;
|
||||
pinnedBrowsers?: Partial<{[key in Browser]: string}>;
|
||||
},
|
||||
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.#allowCachePathOverride = opts.allowCachePathOverride ?? true;
|
||||
this.#pinnedBrowsers = opts.pinnedBrowsers;
|
||||
this.#prefixCommand = opts.prefixCommand;
|
||||
}
|
||||
|
||||
#defineBrowserParameter(yargs: Yargs.Argv<unknown>): void {
|
||||
@ -98,6 +125,9 @@ export class CLI {
|
||||
}
|
||||
|
||||
#definePathParameter(yargs: Yargs.Argv<unknown>, required = false): void {
|
||||
if (!this.#allowCachePathOverride) {
|
||||
return;
|
||||
}
|
||||
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.',
|
||||
@ -111,8 +141,28 @@ export class CLI {
|
||||
|
||||
async run(argv: string[]): Promise<void> {
|
||||
const yargsInstance = yargs(hideBin(argv));
|
||||
await yargsInstance
|
||||
.scriptName('@puppeteer/browsers')
|
||||
let target = yargsInstance.scriptName(this.#scriptName);
|
||||
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()))
|
||||
.parse();
|
||||
}
|
||||
|
||||
#build(yargs: Yargs.Argv<unknown>): Yargs.Argv<unknown> {
|
||||
const latestOrPinned = this.#pinnedBrowsers ? 'pinned' : 'latest';
|
||||
return yargs
|
||||
.command(
|
||||
'install <browser>',
|
||||
'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>).',
|
||||
@ -126,7 +176,7 @@ export class CLI {
|
||||
});
|
||||
yargs.example(
|
||||
'$0 install chrome',
|
||||
'Install the latest available build of the Chrome browser.'
|
||||
`Install the ${latestOrPinned} available build of the Chrome browser.`
|
||||
);
|
||||
yargs.example(
|
||||
'$0 install chrome@latest',
|
||||
@ -176,10 +226,12 @@ export class CLI {
|
||||
'$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.'
|
||||
);
|
||||
}
|
||||
},
|
||||
async argv => {
|
||||
const args = argv as unknown as InstallArgs;
|
||||
@ -187,6 +239,15 @@ export class CLI {
|
||||
if (!args.platform) {
|
||||
throw new Error(`Could not resolve the current platform`);
|
||||
}
|
||||
if (args.browser.buildId === 'pinned') {
|
||||
const pinnedVersion = this.#pinnedBrowsers?.[args.browser.name];
|
||||
if (!pinnedVersion) {
|
||||
throw new Error(
|
||||
`No pinned version found for ${args.browser.name}`
|
||||
);
|
||||
}
|
||||
args.browser.buildId = pinnedVersion;
|
||||
}
|
||||
args.browser.buildId = await resolveBuildId(
|
||||
args.browser.name,
|
||||
args.platform,
|
||||
@ -272,7 +333,9 @@ export class CLI {
|
||||
)
|
||||
.command(
|
||||
'clear',
|
||||
'Removes all installed browsers from the specified cache directory',
|
||||
this.#allowCachePathOverride
|
||||
? 'Removes all installed browsers from the specified cache directory'
|
||||
: `Removes all installed browsers from ${this.#cachePath}`,
|
||||
yargs => {
|
||||
this.#definePathParameter(yargs, true);
|
||||
},
|
||||
@ -296,9 +359,7 @@ export class CLI {
|
||||
}
|
||||
)
|
||||
.demandCommand(1)
|
||||
.help()
|
||||
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|
||||
.parse();
|
||||
.help();
|
||||
}
|
||||
|
||||
#parseBrowser(version: string): Browser {
|
||||
@ -307,7 +368,11 @@ export class CLI {
|
||||
|
||||
#parseBuildId(version: string): string {
|
||||
const parts = version.split('@');
|
||||
return parts.length === 2 ? parts[1]! : 'latest';
|
||||
return parts.length === 2
|
||||
? parts[1]!
|
||||
: this.#pinnedBrowsers
|
||||
? 'pinned'
|
||||
: 'latest';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -423,14 +423,14 @@ export abstract class ProductLauncher {
|
||||
case 'chrome':
|
||||
throw new Error(
|
||||
`Could not find Chrome (ver. ${this.puppeteer.browserRevision}). This can occur if either\n` +
|
||||
' 1. you did not perform an installation before running the script (e.g. `npm install`) or\n' +
|
||||
' 1. you did not perform an installation before running the script (e.g. `npx puppeteer browsers install chrome`) or\n' +
|
||||
` 2. your cache path is incorrectly configured (which is: ${this.puppeteer.configuration.cacheDirectory}).\n` +
|
||||
'For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.'
|
||||
);
|
||||
case 'firefox':
|
||||
throw new Error(
|
||||
`Could not find Firefox (rev. ${this.puppeteer.browserRevision}). This can occur if either\n` +
|
||||
' 1. you did not perform an installation for Firefox before running the script (e.g. `PUPPETEER_PRODUCT=firefox npm install`) or\n' +
|
||||
' 1. you did not perform an installation for Firefox before running the script (e.g. `npx puppeteer browsers install firefox`) or\n' +
|
||||
` 2. your cache path is incorrectly configured (which is: ${this.puppeteer.configuration.cacheDirectory}).\n` +
|
||||
'For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.'
|
||||
);
|
||||
|
@ -9,6 +9,7 @@
|
||||
"automation"
|
||||
],
|
||||
"type": "commonjs",
|
||||
"bin": "./lib/esm/puppeteer/node/cli.js",
|
||||
"main": "./lib/cjs/puppeteer/puppeteer.js",
|
||||
"types": "./lib/types.d.ts",
|
||||
"exports": {
|
||||
@ -77,7 +78,7 @@
|
||||
]
|
||||
},
|
||||
"build:tsc": {
|
||||
"command": "tsc -b",
|
||||
"command": "tsc -b && tsx ../../tools/chmod.ts 755 lib/cjs/puppeteer/node/cli.js lib/esm/puppeteer/node/cli.js",
|
||||
"clean": "if-file-deleted",
|
||||
"dependencies": [
|
||||
"../puppeteer-core:build",
|
||||
|
41
packages/puppeteer/src/node/cli.ts
Normal file
41
packages/puppeteer/src/node/cli.ts
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 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, Browser} from '@puppeteer/browsers';
|
||||
import {PUPPETEER_REVISIONS} from 'puppeteer-core/internal/revisions.js';
|
||||
|
||||
import puppeteer from '../puppeteer.js';
|
||||
|
||||
// TODO: deprecate downloadPath in favour of cacheDirectory.
|
||||
const cacheDir =
|
||||
puppeteer.configuration.downloadPath ??
|
||||
puppeteer.configuration.cacheDirectory!;
|
||||
|
||||
void new CLI({
|
||||
cachePath: cacheDir,
|
||||
scriptName: 'puppeteer',
|
||||
prefixCommand: {
|
||||
cmd: 'browsers',
|
||||
description: 'Manage browsers of this Puppeteer installation',
|
||||
},
|
||||
allowCachePathOverride: false,
|
||||
pinnedBrowsers: {
|
||||
[Browser.CHROME]: PUPPETEER_REVISIONS.chrome,
|
||||
[Browser.FIREFOX]: PUPPETEER_REVISIONS.firefox,
|
||||
},
|
||||
}).run(process.argv);
|
68
test/installation/src/puppeteer-cli.spec.ts
Normal file
68
test/installation/src/puppeteer-cli.spec.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 assert from 'assert';
|
||||
import {spawnSync} from 'child_process';
|
||||
import {existsSync} from 'fs';
|
||||
import {readdir} from 'fs/promises';
|
||||
import {join} from 'path';
|
||||
|
||||
import {configureSandbox} from './sandbox.js';
|
||||
|
||||
describe('Puppeteer CLI', () => {
|
||||
configureSandbox({
|
||||
dependencies: ['@puppeteer/browsers', 'puppeteer-core', 'puppeteer'],
|
||||
env: cwd => {
|
||||
return {
|
||||
PUPPETEER_CACHE_DIR: join(cwd, '.cache', 'puppeteer'),
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
it('can launch', async function () {
|
||||
const result = spawnSync('npx', ['puppeteer', '--help'], {
|
||||
// npx is not found without the shell flag on Windows.
|
||||
shell: process.platform === 'win32',
|
||||
cwd: this.sandbox,
|
||||
});
|
||||
assert.strictEqual(result.status, 0);
|
||||
assert.ok(
|
||||
result.stdout.toString('utf-8').startsWith('puppeteer <command>')
|
||||
);
|
||||
});
|
||||
|
||||
it('can download a browser', async function () {
|
||||
assert.ok(!existsSync(join(this.sandbox, '.cache', 'puppeteer')));
|
||||
const result = spawnSync(
|
||||
'npx',
|
||||
['puppeteer', 'browsers', 'install', 'chrome'],
|
||||
{
|
||||
// npx is not found without the shell flag on Windows.
|
||||
shell: process.platform === 'win32',
|
||||
cwd: this.sandbox,
|
||||
env: {
|
||||
...process.env,
|
||||
PUPPETEER_CACHE_DIR: join(this.sandbox, '.cache', 'puppeteer'),
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.strictEqual(result.status, 0);
|
||||
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
||||
assert.equal(files.length, 1);
|
||||
assert.equal(files[0], 'chrome');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user