blob: 87e8bd0fcab4447094edf3098b0fc12000d8d37c [file] [log] [blame]
Randolf Jungbcb3bc82023-06-26 16:30:141/**
Alex Rudenkoaf11b7c2024-02-06 10:44:422 * @license
3 * Copyright 2023 Google Inc.
4 * SPDX-License-Identifier: Apache-2.0
Randolf Jungbcb3bc82023-06-26 16:30:145 */
Nikolay Vitkov95ea0452025-04-30 15:52:346import fs from 'node:fs';
7import os from 'node:os';
8import path from 'node:path';
Alex Rudenkod04dd452024-02-27 08:25:359import debug from 'debug';
10import { Browser, executablePathByBrowser, getVersionComparator, } from './browser-data/browser-data.js';
Alex Rudenkof7ea7ab2023-10-24 09:40:5111import { detectBrowserPlatform } from './detectPlatform.js';
Alex Rudenkod04dd452024-02-27 08:25:3512const debugCache = debug('puppeteer:browsers:cache');
Randolf Jung3e526312023-08-08 06:20:3913/**
14 * @public
15 */
16export class InstalledBrowser {
17 browser;
18 buildId;
19 platform;
Alex Rudenkof7ea7ab2023-10-24 09:40:5120 executablePath;
Randolf Jung3e526312023-08-08 06:20:3921 #cache;
22 /**
23 * @internal
24 */
25 constructor(cache, browser, buildId, platform) {
26 this.#cache = cache;
27 this.browser = browser;
28 this.buildId = buildId;
29 this.platform = platform;
Alex Rudenkof7ea7ab2023-10-24 09:40:5130 this.executablePath = cache.computeExecutablePath({
31 browser,
32 buildId,
33 platform,
34 });
Randolf Jung3e526312023-08-08 06:20:3935 }
36 /**
37 * Path to the root of the installation folder. Use
38 * {@link computeExecutablePath} to get the path to the executable binary.
39 */
40 get path() {
41 return this.#cache.installationDir(this.browser, this.platform, this.buildId);
42 }
Alex Rudenkod04dd452024-02-27 08:25:3543 readMetadata() {
44 return this.#cache.readMetadata(this.browser);
45 }
46 writeMetadata(metadata) {
47 this.#cache.writeMetadata(this.browser, metadata);
48 }
Randolf Jung3e526312023-08-08 06:20:3949}
Randolf Jungbcb3bc82023-06-26 16:30:1450/**
51 * The cache used by Puppeteer relies on the following structure:
52 *
53 * - rootDir
54 * -- <browser1> | browserRoot(browser1)
55 * ---- <platform>-<buildId> | installationDir()
56 * ------ the browser-platform-buildId
57 * ------ specific structure.
58 * -- <browser2> | browserRoot(browser2)
59 * ---- <platform>-<buildId> | installationDir()
60 * ------ the browser-platform-buildId
61 * ------ specific structure.
62 * @internal
63 */
64export class Cache {
Randolf Jung3e526312023-08-08 06:20:3965 #rootDir;
Randolf Jungbcb3bc82023-06-26 16:30:1466 constructor(rootDir) {
Randolf Jung3e526312023-08-08 06:20:3967 this.#rootDir = rootDir;
68 }
69 /**
70 * @internal
71 */
72 get rootDir() {
73 return this.#rootDir;
Randolf Jungbcb3bc82023-06-26 16:30:1474 }
75 browserRoot(browser) {
Randolf Jung3e526312023-08-08 06:20:3976 return path.join(this.#rootDir, browser);
Randolf Jungbcb3bc82023-06-26 16:30:1477 }
Alex Rudenkod04dd452024-02-27 08:25:3578 metadataFile(browser) {
79 return path.join(this.browserRoot(browser), '.metadata');
80 }
81 readMetadata(browser) {
82 const metatadaPath = this.metadataFile(browser);
83 if (!fs.existsSync(metatadaPath)) {
84 return { aliases: {} };
85 }
86 // TODO: add type-safe parsing.
87 const data = JSON.parse(fs.readFileSync(metatadaPath, 'utf8'));
88 if (typeof data !== 'object') {
89 throw new Error('.metadata is not an object');
90 }
91 return data;
92 }
93 writeMetadata(browser, metadata) {
94 const metatadaPath = this.metadataFile(browser);
95 fs.mkdirSync(path.dirname(metatadaPath), { recursive: true });
96 fs.writeFileSync(metatadaPath, JSON.stringify(metadata, null, 2));
97 }
98 resolveAlias(browser, alias) {
99 const metadata = this.readMetadata(browser);
100 if (alias === 'latest') {
101 return Object.values(metadata.aliases || {})
102 .sort(getVersionComparator(browser))
103 .at(-1);
104 }
105 return metadata.aliases[alias];
106 }
Randolf Jungbcb3bc82023-06-26 16:30:14107 installationDir(browser, platform, buildId) {
108 return path.join(this.browserRoot(browser), `${platform}-${buildId}`);
109 }
110 clear() {
Randolf Jung3e526312023-08-08 06:20:39111 fs.rmSync(this.#rootDir, {
Randolf Jungbcb3bc82023-06-26 16:30:14112 force: true,
113 recursive: true,
114 maxRetries: 10,
115 retryDelay: 500,
116 });
117 }
118 uninstall(browser, platform, buildId) {
Alex Rudenkod04dd452024-02-27 08:25:35119 const metadata = this.readMetadata(browser);
120 for (const alias of Object.keys(metadata.aliases)) {
121 if (metadata.aliases[alias] === buildId) {
122 delete metadata.aliases[alias];
123 }
124 }
Randolf Jungbcb3bc82023-06-26 16:30:14125 fs.rmSync(this.installationDir(browser, platform, buildId), {
126 force: true,
127 recursive: true,
128 maxRetries: 10,
129 retryDelay: 500,
130 });
131 }
132 getInstalledBrowsers() {
Randolf Jung3e526312023-08-08 06:20:39133 if (!fs.existsSync(this.#rootDir)) {
Randolf Jungbcb3bc82023-06-26 16:30:14134 return [];
135 }
Randolf Jung3e526312023-08-08 06:20:39136 const types = fs.readdirSync(this.#rootDir);
Randolf Jungbcb3bc82023-06-26 16:30:14137 const browsers = types.filter((t) => {
138 return Object.values(Browser).includes(t);
139 });
140 return browsers.flatMap(browser => {
141 const files = fs.readdirSync(this.browserRoot(browser));
142 return files
143 .map(file => {
144 const result = parseFolderPath(path.join(this.browserRoot(browser), file));
145 if (!result) {
146 return null;
147 }
Randolf Jung3e526312023-08-08 06:20:39148 return new InstalledBrowser(this, browser, result.buildId, result.platform);
Randolf Jungbcb3bc82023-06-26 16:30:14149 })
150 .filter((item) => {
151 return item !== null;
152 });
153 });
154 }
Alex Rudenkof7ea7ab2023-10-24 09:40:51155 computeExecutablePath(options) {
156 options.platform ??= detectBrowserPlatform();
157 if (!options.platform) {
158 throw new Error(`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`);
159 }
Alex Rudenkod04dd452024-02-27 08:25:35160 try {
161 options.buildId =
162 this.resolveAlias(options.browser, options.buildId) ?? options.buildId;
163 }
164 catch {
165 debugCache('could not read .metadata file for the browser');
166 }
Alex Rudenkof7ea7ab2023-10-24 09:40:51167 const installationDir = this.installationDir(options.browser, options.platform, options.buildId);
168 return path.join(installationDir, executablePathByBrowser[options.browser](options.platform, options.buildId));
169 }
Randolf Jungbcb3bc82023-06-26 16:30:14170}
Randolf Jungbcb3bc82023-06-26 16:30:14171function parseFolderPath(folderPath) {
172 const name = path.basename(folderPath);
173 const splits = name.split('-');
174 if (splits.length !== 2) {
175 return;
176 }
177 const [platform, buildId] = splits;
178 if (!buildId || !platform) {
179 return;
180 }
181 return { platform, buildId };
182}
183//# sourceMappingURL=Cache.js.map