mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
feat: download chrome-headless-shell by default and use it for the old headless mode (#11093)
This commit is contained in:
parent
e956daa7fa
commit
75ba27bca2
@ -46,10 +46,10 @@ pnpm i puppeteer
|
|||||||
```
|
```
|
||||||
|
|
||||||
When you install Puppeteer, it automatically downloads a recent version of
|
When you install Puppeteer, it automatically downloads a recent version of
|
||||||
[Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (~170MB macOS, ~282MB Linux, ~280MB Windows) that is [guaranteed to
|
[Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (~170MB macOS, ~282MB Linux, ~280MB Windows) and a `chrome-headless-shell` binary (starting with Puppeteer v21.6.0) that is [guaranteed to
|
||||||
work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy)
|
work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy)
|
||||||
with Puppeteer. The browser is downloaded to the `$HOME/.cache/puppeteer` folder
|
with Puppeteer. The browser is downloaded to the `$HOME/.cache/puppeteer` folder
|
||||||
by default (starting with Puppeteer v19.0.0).
|
by default (starting with Puppeteer v19.0.0). See [configuration](https://pptr.dev/api/puppeteer.configuration) for configuration options and environmental variables to control the download behavor.
|
||||||
|
|
||||||
If you deploy a project using Puppeteer to a hosting provider, such as Render or
|
If you deploy a project using Puppeteer to a hosting provider, such as Render or
|
||||||
Heroku, you might need to reconfigure the location of the cache to be within
|
Heroku, you might need to reconfigure the location of the cache to be within
|
||||||
|
@ -17,7 +17,7 @@ export interface Configuration
|
|||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
| Property | Modifiers | Type | Description | Default |
|
| Property | Modifiers | Type | Description | Default |
|
||||||
| ------------------ | --------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------- | --------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| browserRevision | <code>optional</code> | string | <p>Specifies a certain version of the browser you'd like Puppeteer to use.</p><p>Can be overridden by <code>PUPPETEER_BROWSER_REVISION</code>.</p><p>See [puppeteer.launch](./puppeteer.puppeteernode.launch.md) on how executable path is inferred.</p> | A compatible-revision of the browser. |
|
| browserRevision | <code>optional</code> | string | <p>Specifies a certain version of the browser you'd like Puppeteer to use.</p><p>Can be overridden by <code>PUPPETEER_BROWSER_REVISION</code>.</p><p>See [puppeteer.launch](./puppeteer.puppeteernode.launch.md) on how executable path is inferred.</p> | A compatible-revision of the browser. |
|
||||||
| cacheDirectory | <code>optional</code> | string | <p>Defines the directory to be used by Puppeteer for caching.</p><p>Can be overridden by <code>PUPPETEER_CACHE_DIR</code>.</p> | <code>path.join(os.homedir(), '.cache', 'puppeteer')</code> |
|
| cacheDirectory | <code>optional</code> | string | <p>Defines the directory to be used by Puppeteer for caching.</p><p>Can be overridden by <code>PUPPETEER_CACHE_DIR</code>.</p> | <code>path.join(os.homedir(), '.cache', 'puppeteer')</code> |
|
||||||
| defaultProduct | <code>optional</code> | [Product](./puppeteer.product.md) | <p>Specifies which browser you'd like Puppeteer to use.</p><p>Can be overridden by <code>PUPPETEER_PRODUCT</code>.</p> | <code>chrome</code> |
|
| defaultProduct | <code>optional</code> | [Product](./puppeteer.product.md) | <p>Specifies which browser you'd like Puppeteer to use.</p><p>Can be overridden by <code>PUPPETEER_PRODUCT</code>.</p> | <code>chrome</code> |
|
||||||
@ -26,5 +26,7 @@ export interface Configuration
|
|||||||
| executablePath | <code>optional</code> | string | <p>Specifies an executable path to be used in [puppeteer.launch](./puppeteer.puppeteernode.launch.md).</p><p>Can be overridden by <code>PUPPETEER_EXECUTABLE_PATH</code>.</p> | **Auto-computed.** |
|
| executablePath | <code>optional</code> | string | <p>Specifies an executable path to be used in [puppeteer.launch](./puppeteer.puppeteernode.launch.md).</p><p>Can be overridden by <code>PUPPETEER_EXECUTABLE_PATH</code>.</p> | **Auto-computed.** |
|
||||||
| experiments | <code>optional</code> | [ExperimentsConfiguration](./puppeteer.experimentsconfiguration.md) | Defines experimental options for Puppeteer. | |
|
| experiments | <code>optional</code> | [ExperimentsConfiguration](./puppeteer.experimentsconfiguration.md) | Defines experimental options for Puppeteer. | |
|
||||||
| logLevel | <code>optional</code> | 'silent' \| 'error' \| 'warn' | Tells Puppeteer to log at the given level. | <code>warn</code> |
|
| logLevel | <code>optional</code> | 'silent' \| 'error' \| 'warn' | Tells Puppeteer to log at the given level. | <code>warn</code> |
|
||||||
|
| skipChromeDownload | <code>optional</code> | boolean | <p>Tells Puppeteer to not Chrome download during installation.</p><p>Can be overridden by <code>PUPPETEER_SKIP_CHROME_DOWNLOAD</code>.</p> | |
|
||||||
|
| skipChromeHeadlessShellDownload | <code>optional</code> | boolean | <p>Tells Puppeteer to not chrome-headless-shell download during installation.</p><p>Can be overridden by <code>PUPPETEER_SKIP_CHROME_HEADLESSS_HELL_DOWNLOAD</code>.</p> | |
|
||||||
| skipDownload | <code>optional</code> | boolean | <p>Tells Puppeteer to not download during installation.</p><p>Can be overridden by <code>PUPPETEER_SKIP_DOWNLOAD</code>.</p> | |
|
| skipDownload | <code>optional</code> | boolean | <p>Tells Puppeteer to not download during installation.</p><p>Can be overridden by <code>PUPPETEER_SKIP_DOWNLOAD</code>.</p> | |
|
||||||
| temporaryDirectory | <code>optional</code> | string | <p>Defines the directory to be used by Puppeteer for creating temporary files.</p><p>Can be overridden by <code>PUPPETEER_TMP_DIR</code>.</p> | <code>os.tmpdir()</code> |
|
| temporaryDirectory | <code>optional</code> | string | <p>Defines the directory to be used by Puppeteer for creating temporary files.</p><p>Can be overridden by <code>PUPPETEER_TMP_DIR</code>.</p> | <code>os.tmpdir()</code> |
|
||||||
|
@ -46,10 +46,10 @@ pnpm i puppeteer
|
|||||||
```
|
```
|
||||||
|
|
||||||
When you install Puppeteer, it automatically downloads a recent version of
|
When you install Puppeteer, it automatically downloads a recent version of
|
||||||
[Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (~170MB macOS, ~282MB Linux, ~280MB Windows) that is [guaranteed to
|
[Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (~170MB macOS, ~282MB Linux, ~280MB Windows) and a `chrome-headless-shell` binary (starting with Puppeteer v21.6.0) that is [guaranteed to
|
||||||
work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy)
|
work](https://pptr.dev/faq#q-why-doesnt-puppeteer-vxxx-work-with-chromium-vyyy)
|
||||||
with Puppeteer. The browser is downloaded to the `$HOME/.cache/puppeteer` folder
|
with Puppeteer. The browser is downloaded to the `$HOME/.cache/puppeteer` folder
|
||||||
by default (starting with Puppeteer v19.0.0).
|
by default (starting with Puppeteer v19.0.0). See [configuration](https://pptr.dev/api/puppeteer.configuration) for configuration options and environmental variables to control the download behavor.
|
||||||
|
|
||||||
If you deploy a project using Puppeteer to a hosting provider, such as Render or
|
If you deploy a project using Puppeteer to a hosting provider, such as Render or
|
||||||
Heroku, you might need to reconfigure the location of the cache to be within
|
Heroku, you might need to reconfigure the location of the cache to be within
|
||||||
|
@ -105,6 +105,18 @@ export interface Configuration {
|
|||||||
* Can be overridden by `PUPPETEER_SKIP_DOWNLOAD`.
|
* Can be overridden by `PUPPETEER_SKIP_DOWNLOAD`.
|
||||||
*/
|
*/
|
||||||
skipDownload?: boolean;
|
skipDownload?: boolean;
|
||||||
|
/**
|
||||||
|
* Tells Puppeteer to not Chrome download during installation.
|
||||||
|
*
|
||||||
|
* Can be overridden by `PUPPETEER_SKIP_CHROME_DOWNLOAD`.
|
||||||
|
*/
|
||||||
|
skipChromeDownload?: boolean;
|
||||||
|
/**
|
||||||
|
* Tells Puppeteer to not chrome-headless-shell download during installation.
|
||||||
|
*
|
||||||
|
* Can be overridden by `PUPPETEER_SKIP_CHROME_HEADLESSS_HELL_DOWNLOAD`.
|
||||||
|
*/
|
||||||
|
skipChromeHeadlessShellDownload?: boolean;
|
||||||
/**
|
/**
|
||||||
* Tells Puppeteer to log at the given level.
|
* Tells Puppeteer to log at the given level.
|
||||||
*
|
*
|
||||||
|
@ -156,7 +156,7 @@ export class ChromeLauncher extends ProductLauncher {
|
|||||||
channel || !this.puppeteer._isPuppeteerCore,
|
channel || !this.puppeteer._isPuppeteerCore,
|
||||||
`An \`executablePath\` or \`channel\` must be specified for \`puppeteer-core\``
|
`An \`executablePath\` or \`channel\` must be specified for \`puppeteer-core\``
|
||||||
);
|
);
|
||||||
chromeExecutable = this.executablePath(channel);
|
chromeExecutable = this.executablePath(channel, options.headless ?? true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -279,14 +279,17 @@ export class ChromeLauncher extends ProductLauncher {
|
|||||||
return chromeArguments;
|
return chromeArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
override executablePath(channel?: ChromeReleaseChannel): string {
|
override executablePath(
|
||||||
|
channel?: ChromeReleaseChannel,
|
||||||
|
headless?: boolean | 'new'
|
||||||
|
): string {
|
||||||
if (channel) {
|
if (channel) {
|
||||||
return computeSystemExecutablePath({
|
return computeSystemExecutablePath({
|
||||||
browser: SupportedBrowsers.CHROME,
|
browser: SupportedBrowsers.CHROME,
|
||||||
channel: convertPuppeteerChannelToBrowsersChannel(channel),
|
channel: convertPuppeteerChannelToBrowsersChannel(channel),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return this.resolveExecutablePath();
|
return this.resolveExecutablePath(headless);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,7 +386,7 @@ export abstract class ProductLauncher {
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
protected resolveExecutablePath(): string {
|
protected resolveExecutablePath(headless?: boolean | 'new'): string {
|
||||||
let executablePath = this.puppeteer.configuration.executablePath;
|
let executablePath = this.puppeteer.configuration.executablePath;
|
||||||
if (executablePath) {
|
if (executablePath) {
|
||||||
if (!existsSync(executablePath)) {
|
if (!existsSync(executablePath)) {
|
||||||
@ -397,9 +397,12 @@ export abstract class ProductLauncher {
|
|||||||
return executablePath;
|
return executablePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function productToBrowser(product?: Product) {
|
function productToBrowser(product?: Product, headless?: boolean | 'new') {
|
||||||
switch (product) {
|
switch (product) {
|
||||||
case 'chrome':
|
case 'chrome':
|
||||||
|
if (headless === true) {
|
||||||
|
return InstalledBrowser.CHROMEHEADLESSSHELL;
|
||||||
|
}
|
||||||
return InstalledBrowser.CHROME;
|
return InstalledBrowser.CHROME;
|
||||||
case 'firefox':
|
case 'firefox':
|
||||||
return InstalledBrowser.FIREFOX;
|
return InstalledBrowser.FIREFOX;
|
||||||
@ -409,7 +412,7 @@ export abstract class ProductLauncher {
|
|||||||
|
|
||||||
executablePath = computeExecutablePath({
|
executablePath = computeExecutablePath({
|
||||||
cacheDir: this.puppeteer.defaultDownloadPath!,
|
cacheDir: this.puppeteer.defaultDownloadPath!,
|
||||||
browser: productToBrowser(this.product),
|
browser: productToBrowser(this.product, headless),
|
||||||
buildId: this.puppeteer.browserRevision,
|
buildId: this.puppeteer.browserRevision,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,5 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
export const PUPPETEER_REVISIONS = Object.freeze({
|
export const PUPPETEER_REVISIONS = Object.freeze({
|
||||||
chrome: '120.0.6099.71',
|
chrome: '120.0.6099.71',
|
||||||
|
'chrome-headless-shell': '120.0.6099.71',
|
||||||
firefox: 'latest',
|
firefox: 'latest',
|
||||||
});
|
});
|
||||||
|
@ -72,6 +72,24 @@ export const getConfiguration = (): Configuration => {
|
|||||||
configuration.skipDownload
|
configuration.skipDownload
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Set skipChromeDownload explicitly or from default
|
||||||
|
configuration.skipChromeDownload = Boolean(
|
||||||
|
process.env['PUPPETEER_SKIP_CHROME_DOWNLOAD'] ??
|
||||||
|
process.env['npm_config_puppeteer_skip_chrome_download'] ??
|
||||||
|
process.env['npm_package_config_puppeteer_skip_chrome_download'] ??
|
||||||
|
configuration.skipChromeDownload
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set skipChromeDownload explicitly or from default
|
||||||
|
configuration.skipChromeHeadlessShellDownload = Boolean(
|
||||||
|
process.env['PUPPETEER_SKIP_CHROME_HEADLESS_SHELL_DOWNLOAD'] ??
|
||||||
|
process.env['npm_config_puppeteer_skip_chrome_headless_shell_download'] ??
|
||||||
|
process.env[
|
||||||
|
'npm_package_config_puppeteer_skip_chrome_headless_shell_download'
|
||||||
|
] ??
|
||||||
|
configuration.skipChromeHeadlessShellDownload
|
||||||
|
);
|
||||||
|
|
||||||
// Prepare variables used in browser downloading
|
// Prepare variables used in browser downloading
|
||||||
if (!configuration.skipDownload) {
|
if (!configuration.skipDownload) {
|
||||||
configuration.browserRevision =
|
configuration.browserRevision =
|
||||||
|
@ -37,5 +37,6 @@ void new CLI({
|
|||||||
pinnedBrowsers: {
|
pinnedBrowsers: {
|
||||||
[Browser.CHROME]: PUPPETEER_REVISIONS.chrome,
|
[Browser.CHROME]: PUPPETEER_REVISIONS.chrome,
|
||||||
[Browser.FIREFOX]: PUPPETEER_REVISIONS.firefox,
|
[Browser.FIREFOX]: PUPPETEER_REVISIONS.firefox,
|
||||||
|
[Browser.CHROMEHEADLESSSHELL]: PUPPETEER_REVISIONS['chrome-headless-shell'],
|
||||||
},
|
},
|
||||||
}).run(process.argv);
|
}).run(process.argv);
|
||||||
|
@ -58,24 +58,64 @@ export async function downloadBrowser(): Promise<void> {
|
|||||||
|
|
||||||
const unresolvedBuildId =
|
const unresolvedBuildId =
|
||||||
configuration.browserRevision || PUPPETEER_REVISIONS[product] || 'latest';
|
configuration.browserRevision || PUPPETEER_REVISIONS[product] || 'latest';
|
||||||
|
const unresolvedShellBuildId =
|
||||||
|
configuration.browserRevision ||
|
||||||
|
PUPPETEER_REVISIONS['chrome-headless-shell'] ||
|
||||||
|
'latest';
|
||||||
|
|
||||||
|
const shellBuildId =
|
||||||
|
browser === Browser.CHROME
|
||||||
|
? await resolveBuildId(browser, platform, unresolvedShellBuildId)
|
||||||
|
: '';
|
||||||
|
|
||||||
const buildId = await resolveBuildId(browser, platform, unresolvedBuildId);
|
const buildId = await resolveBuildId(browser, platform, unresolvedBuildId);
|
||||||
// TODO: deprecate downloadPath in favour of cacheDirectory.
|
// TODO: deprecate downloadPath in favour of cacheDirectory.
|
||||||
const cacheDir = configuration.downloadPath ?? configuration.cacheDirectory!;
|
const cacheDir = configuration.downloadPath ?? configuration.cacheDirectory!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await install({
|
const installationJobs = [];
|
||||||
|
|
||||||
|
if (configuration.skipChromeDownload) {
|
||||||
|
logPolitely('**INFO** Skipping Chrome download as instructed.');
|
||||||
|
} else {
|
||||||
|
installationJobs.push(
|
||||||
|
install({
|
||||||
browser,
|
browser,
|
||||||
cacheDir,
|
cacheDir,
|
||||||
platform,
|
platform,
|
||||||
buildId,
|
buildId,
|
||||||
downloadProgressCallback: makeProgressCallback(browser, buildId),
|
downloadProgressCallback: makeProgressCallback(browser, buildId),
|
||||||
baseUrl: downloadBaseUrl,
|
baseUrl: downloadBaseUrl,
|
||||||
});
|
}).then(result => {
|
||||||
|
|
||||||
logPolitely(
|
logPolitely(
|
||||||
`${supportedProducts[product]} (${result.buildId}) downloaded to ${result.path}`
|
`${supportedProducts[product]} (${result.buildId}) downloaded to ${result.path}`
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser === Browser.CHROME) {
|
||||||
|
if (configuration.skipChromeHeadlessShellDownload) {
|
||||||
|
logPolitely('**INFO** Skipping Chrome download as instructed.');
|
||||||
|
} else {
|
||||||
|
installationJobs.push(
|
||||||
|
install({
|
||||||
|
browser: Browser.CHROMEHEADLESSSHELL,
|
||||||
|
cacheDir,
|
||||||
|
platform,
|
||||||
|
buildId: shellBuildId,
|
||||||
|
downloadProgressCallback: makeProgressCallback(browser, buildId),
|
||||||
|
baseUrl: downloadBaseUrl,
|
||||||
|
}).then(result => {
|
||||||
|
logPolitely(
|
||||||
|
`${Browser.CHROMEHEADLESSSHELL} (${result.buildId}) downloaded to ${result.path}`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(installationJobs);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`ERROR: Failed to set up ${supportedProducts[product]} r${buildId}! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download.`
|
`ERROR: Failed to set up ${supportedProducts[product]} r${buildId}! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to skip download.`
|
||||||
|
@ -40,8 +40,9 @@ describe('`puppeteer` with configuration', () => {
|
|||||||
|
|
||||||
it('evaluates', async function () {
|
it('evaluates', async function () {
|
||||||
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
||||||
assert.equal(files.length, 1);
|
assert.equal(files.length, 2);
|
||||||
assert.equal(files[0], 'chrome');
|
assert(files.includes('chrome'));
|
||||||
|
assert(files.includes('chrome-headless-shell'));
|
||||||
|
|
||||||
const script = await readAsset('puppeteer', 'basic.js');
|
const script = await readAsset('puppeteer', 'basic.js');
|
||||||
await this.runScript(script, 'mjs');
|
await this.runScript(script, 'mjs');
|
||||||
@ -71,8 +72,9 @@ describe('`puppeteer` with configuration', () => {
|
|||||||
|
|
||||||
it('evaluates', async function () {
|
it('evaluates', async function () {
|
||||||
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
||||||
assert.equal(files.length, 1);
|
assert.equal(files.length, 2);
|
||||||
assert.equal(files[0], 'chrome');
|
assert(files.includes('chrome'));
|
||||||
|
assert(files.includes('chrome-headless-shell'));
|
||||||
|
|
||||||
const script = await readAsset('puppeteer', 'basic.js');
|
const script = await readAsset('puppeteer', 'basic.js');
|
||||||
await this.runScript(script, 'mjs');
|
await this.runScript(script, 'mjs');
|
||||||
|
@ -35,8 +35,10 @@ describe('`puppeteer`', () => {
|
|||||||
|
|
||||||
it('evaluates CommonJS', async function () {
|
it('evaluates CommonJS', async function () {
|
||||||
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
||||||
assert.equal(files.length, 1);
|
assert.equal(files.length, 2);
|
||||||
assert.equal(files[0], 'chrome');
|
assert(files.includes('chrome'));
|
||||||
|
assert(files.includes('chrome-headless-shell'));
|
||||||
|
|
||||||
const script = await readAsset('puppeteer-core', 'requires.cjs');
|
const script = await readAsset('puppeteer-core', 'requires.cjs');
|
||||||
await this.runScript(script, 'cjs');
|
await this.runScript(script, 'cjs');
|
||||||
});
|
});
|
||||||
@ -62,8 +64,9 @@ describe('`puppeteer`', () => {
|
|||||||
|
|
||||||
it('evaluates', async function () {
|
it('evaluates', async function () {
|
||||||
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
const files = await readdir(join(this.sandbox, '.cache', 'puppeteer'));
|
||||||
assert.equal(files.length, 1);
|
assert.equal(files.length, 2);
|
||||||
assert.equal(files[0], 'chrome');
|
assert(files.includes('chrome'));
|
||||||
|
assert(files.includes('chrome-headless-shell'));
|
||||||
|
|
||||||
const script = await readAsset('puppeteer', 'basic.js');
|
const script = await readAsset('puppeteer', 'basic.js');
|
||||||
await this.runScript(script, 'mjs');
|
await this.runScript(script, 'mjs');
|
||||||
|
@ -72,7 +72,7 @@ async function formatUpdateFiles() {
|
|||||||
|
|
||||||
async function replaceInFile(filePath, search, replace) {
|
async function replaceInFile(filePath, search, replace) {
|
||||||
const buffer = await readFile(filePath);
|
const buffer = await readFile(filePath);
|
||||||
const update = buffer.toString().replace(search, replace);
|
const update = buffer.toString().replaceAll(search, replace);
|
||||||
|
|
||||||
await writeFile(filePath, update);
|
await writeFile(filePath, update);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user