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
|
```typescript
|
||||||
class CLI {
|
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
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| 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)_ |
|
| rl | readline.Interface | _(Optional)_ |
|
||||||
|
@ -13,8 +13,8 @@ export declare class CLI
|
|||||||
## Constructors
|
## Constructors
|
||||||
|
|
||||||
| Constructor | Modifiers | Description |
|
| 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
|
## Methods
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ again.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
# Or to download Firefox
|
# Or to download Firefox by default
|
||||||
PUPPETEER_PRODUCT=firefox npm install
|
PUPPETEER_PRODUCT=firefox npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -117,6 +117,12 @@ To fetch Firefox Nightly as part of Puppeteer installation:
|
|||||||
PUPPETEER_PRODUCT=firefox npm i puppeteer
|
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”?
|
#### Q: What’s considered a “Navigation”?
|
||||||
|
|
||||||
From Puppeteer’s standpoint, **“navigation” is anything that changes a page’s
|
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",
|
"cosmiconfig": "8.3.6",
|
||||||
"puppeteer-core": "21.5.2"
|
"puppeteer-core": "21.5.2"
|
||||||
},
|
},
|
||||||
|
"bin": {
|
||||||
|
"puppeteer": "lib/esm/puppeteer/node/cli.js"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.17.15"
|
"@types/node": "18.17.15"
|
||||||
},
|
},
|
||||||
|
@ -68,10 +68,37 @@ interface ClearArgs {
|
|||||||
export class CLI {
|
export class CLI {
|
||||||
#cachePath;
|
#cachePath;
|
||||||
#rl?: readline.Interface;
|
#rl?: readline.Interface;
|
||||||
|
#scriptName = '';
|
||||||
|
#allowCachePathOverride = true;
|
||||||
|
#pinnedBrowsers?: Partial<{[key in Browser]: string}>;
|
||||||
|
#prefixCommand?: {cmd: string; description: string};
|
||||||
|
|
||||||
constructor(cachePath = process.cwd(), rl?: readline.Interface) {
|
constructor(
|
||||||
this.#cachePath = cachePath;
|
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.#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 {
|
#defineBrowserParameter(yargs: Yargs.Argv<unknown>): void {
|
||||||
@ -98,6 +125,9 @@ export class CLI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#definePathParameter(yargs: Yargs.Argv<unknown>, required = false): void {
|
#definePathParameter(yargs: Yargs.Argv<unknown>, required = false): void {
|
||||||
|
if (!this.#allowCachePathOverride) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
yargs.option('path', {
|
yargs.option('path', {
|
||||||
type: 'string',
|
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.',
|
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> {
|
async run(argv: string[]): Promise<void> {
|
||||||
const yargsInstance = yargs(hideBin(argv));
|
const yargsInstance = yargs(hideBin(argv));
|
||||||
await yargsInstance
|
let target = yargsInstance.scriptName(this.#scriptName);
|
||||||
.scriptName('@puppeteer/browsers')
|
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(
|
.command(
|
||||||
'install <browser>',
|
'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>).',
|
'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(
|
yargs.example(
|
||||||
'$0 install chrome',
|
'$0 install chrome',
|
||||||
'Install the latest available build of the Chrome browser.'
|
`Install the ${latestOrPinned} available build of the Chrome browser.`
|
||||||
);
|
);
|
||||||
yargs.example(
|
yargs.example(
|
||||||
'$0 install chrome@latest',
|
'$0 install chrome@latest',
|
||||||
@ -176,10 +226,12 @@ export class CLI {
|
|||||||
'$0 install firefox --platform mac',
|
'$0 install firefox --platform mac',
|
||||||
'Install the latest Mac (Intel) build of the Firefox browser.'
|
'Install the latest Mac (Intel) build of the Firefox browser.'
|
||||||
);
|
);
|
||||||
|
if (this.#allowCachePathOverride) {
|
||||||
yargs.example(
|
yargs.example(
|
||||||
'$0 install firefox --path /tmp/my-browser-cache',
|
'$0 install firefox --path /tmp/my-browser-cache',
|
||||||
'Install to the specified cache directory.'
|
'Install to the specified cache directory.'
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async argv => {
|
async argv => {
|
||||||
const args = argv as unknown as InstallArgs;
|
const args = argv as unknown as InstallArgs;
|
||||||
@ -187,6 +239,15 @@ export class CLI {
|
|||||||
if (!args.platform) {
|
if (!args.platform) {
|
||||||
throw new Error(`Could not resolve the current 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.buildId = await resolveBuildId(
|
||||||
args.browser.name,
|
args.browser.name,
|
||||||
args.platform,
|
args.platform,
|
||||||
@ -272,7 +333,9 @@ export class CLI {
|
|||||||
)
|
)
|
||||||
.command(
|
.command(
|
||||||
'clear',
|
'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 => {
|
yargs => {
|
||||||
this.#definePathParameter(yargs, true);
|
this.#definePathParameter(yargs, true);
|
||||||
},
|
},
|
||||||
@ -296,9 +359,7 @@ export class CLI {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.demandCommand(1)
|
.demandCommand(1)
|
||||||
.help()
|
.help();
|
||||||
.wrap(Math.min(120, yargsInstance.terminalWidth()))
|
|
||||||
.parse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#parseBrowser(version: string): Browser {
|
#parseBrowser(version: string): Browser {
|
||||||
@ -307,7 +368,11 @@ export class CLI {
|
|||||||
|
|
||||||
#parseBuildId(version: string): string {
|
#parseBuildId(version: string): string {
|
||||||
const parts = version.split('@');
|
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':
|
case 'chrome':
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not find Chrome (ver. ${this.puppeteer.browserRevision}). This can occur if either\n` +
|
`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` +
|
` 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.'
|
'For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.'
|
||||||
);
|
);
|
||||||
case 'firefox':
|
case 'firefox':
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not find Firefox (rev. ${this.puppeteer.browserRevision}). This can occur if either\n` +
|
`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` +
|
` 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.'
|
'For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.'
|
||||||
);
|
);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"automation"
|
"automation"
|
||||||
],
|
],
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
|
"bin": "./lib/esm/puppeteer/node/cli.js",
|
||||||
"main": "./lib/cjs/puppeteer/puppeteer.js",
|
"main": "./lib/cjs/puppeteer/puppeteer.js",
|
||||||
"types": "./lib/types.d.ts",
|
"types": "./lib/types.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
@ -77,7 +78,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"build:tsc": {
|
"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",
|
"clean": "if-file-deleted",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"../puppeteer-core:build",
|
"../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