install.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /**
  2. * @license
  3. * Copyright 2017 Google Inc.
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. import assert from 'assert';
  7. import { existsSync } from 'fs';
  8. import { mkdir, unlink } from 'fs/promises';
  9. import os from 'os';
  10. import path from 'path';
  11. import { Browser, BrowserPlatform, downloadUrls, } from './browser-data/browser-data.js';
  12. import { Cache, InstalledBrowser } from './Cache.js';
  13. import { debug } from './debug.js';
  14. import { detectBrowserPlatform } from './detectPlatform.js';
  15. import { unpackArchive } from './fileUtil.js';
  16. import { downloadFile, getJSON, headHttpRequest } from './httpUtil.js';
  17. const debugInstall = debug('puppeteer:browsers:install');
  18. const times = new Map();
  19. function debugTime(label) {
  20. times.set(label, process.hrtime());
  21. }
  22. function debugTimeEnd(label) {
  23. const end = process.hrtime();
  24. const start = times.get(label);
  25. if (!start) {
  26. return;
  27. }
  28. const duration = end[0] * 1000 + end[1] / 1e6 - (start[0] * 1000 + start[1] / 1e6); // calculate duration in milliseconds
  29. debugInstall(`Duration for ${label}: ${duration}ms`);
  30. }
  31. export async function install(options) {
  32. options.platform ??= detectBrowserPlatform();
  33. options.unpack ??= true;
  34. if (!options.platform) {
  35. throw new Error(`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`);
  36. }
  37. const url = getDownloadUrl(options.browser, options.platform, options.buildId, options.baseUrl);
  38. try {
  39. return await installUrl(url, options);
  40. }
  41. catch (err) {
  42. // If custom baseUrl is provided, do not fall back to CfT dashboard.
  43. if (options.baseUrl && !options.forceFallbackForTesting) {
  44. throw err;
  45. }
  46. debugInstall(`Error downloading from ${url}.`);
  47. switch (options.browser) {
  48. case Browser.CHROME:
  49. case Browser.CHROMEDRIVER:
  50. case Browser.CHROMEHEADLESSSHELL: {
  51. debugInstall(`Trying to find download URL via https://googlechromelabs.github.io/chrome-for-testing.`);
  52. const version = (await getJSON(new URL(`https://googlechromelabs.github.io/chrome-for-testing/${options.buildId}.json`)));
  53. let platform = '';
  54. switch (options.platform) {
  55. case BrowserPlatform.LINUX:
  56. platform = 'linux64';
  57. break;
  58. case BrowserPlatform.MAC_ARM:
  59. platform = 'mac-arm64';
  60. break;
  61. case BrowserPlatform.MAC:
  62. platform = 'mac-x64';
  63. break;
  64. case BrowserPlatform.WIN32:
  65. platform = 'win32';
  66. break;
  67. case BrowserPlatform.WIN64:
  68. platform = 'win64';
  69. break;
  70. }
  71. const url = version.downloads[options.browser]?.find(link => {
  72. return link['platform'] === platform;
  73. })?.url;
  74. if (url) {
  75. debugInstall(`Falling back to downloading from ${url}.`);
  76. return await installUrl(new URL(url), options);
  77. }
  78. throw err;
  79. }
  80. default:
  81. throw err;
  82. }
  83. }
  84. }
  85. async function installUrl(url, options) {
  86. options.platform ??= detectBrowserPlatform();
  87. if (!options.platform) {
  88. throw new Error(`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`);
  89. }
  90. const fileName = url.toString().split('/').pop();
  91. assert(fileName, `A malformed download URL was found: ${url}.`);
  92. const cache = new Cache(options.cacheDir);
  93. const browserRoot = cache.browserRoot(options.browser);
  94. const archivePath = path.join(browserRoot, `${options.buildId}-${fileName}`);
  95. if (!existsSync(browserRoot)) {
  96. await mkdir(browserRoot, { recursive: true });
  97. }
  98. if (!options.unpack) {
  99. if (existsSync(archivePath)) {
  100. return archivePath;
  101. }
  102. debugInstall(`Downloading binary from ${url}`);
  103. debugTime('download');
  104. await downloadFile(url, archivePath, options.downloadProgressCallback);
  105. debugTimeEnd('download');
  106. return archivePath;
  107. }
  108. const outputPath = cache.installationDir(options.browser, options.platform, options.buildId);
  109. try {
  110. if (existsSync(outputPath)) {
  111. const installedBrowser = new InstalledBrowser(cache, options.browser, options.buildId, options.platform);
  112. if (!existsSync(installedBrowser.executablePath)) {
  113. throw new Error(`The browser folder (${outputPath}) exists but the executable (${installedBrowser.executablePath}) is missing`);
  114. }
  115. return installedBrowser;
  116. }
  117. debugInstall(`Downloading binary from ${url}`);
  118. try {
  119. debugTime('download');
  120. await downloadFile(url, archivePath, options.downloadProgressCallback);
  121. }
  122. finally {
  123. debugTimeEnd('download');
  124. }
  125. debugInstall(`Installing ${archivePath} to ${outputPath}`);
  126. try {
  127. debugTime('extract');
  128. await unpackArchive(archivePath, outputPath);
  129. }
  130. finally {
  131. debugTimeEnd('extract');
  132. }
  133. const installedBrowser = new InstalledBrowser(cache, options.browser, options.buildId, options.platform);
  134. if (options.buildIdAlias) {
  135. const metadata = installedBrowser.readMetadata();
  136. metadata.aliases[options.buildIdAlias] = options.buildId;
  137. installedBrowser.writeMetadata(metadata);
  138. }
  139. return installedBrowser;
  140. }
  141. finally {
  142. if (existsSync(archivePath)) {
  143. await unlink(archivePath);
  144. }
  145. }
  146. }
  147. /**
  148. *
  149. * @public
  150. */
  151. export async function uninstall(options) {
  152. options.platform ??= detectBrowserPlatform();
  153. if (!options.platform) {
  154. throw new Error(`Cannot detect the browser platform for: ${os.platform()} (${os.arch()})`);
  155. }
  156. new Cache(options.cacheDir).uninstall(options.browser, options.platform, options.buildId);
  157. }
  158. /**
  159. * Returns metadata about browsers installed in the cache directory.
  160. *
  161. * @public
  162. */
  163. export async function getInstalledBrowsers(options) {
  164. return new Cache(options.cacheDir).getInstalledBrowsers();
  165. }
  166. /**
  167. * @public
  168. */
  169. export async function canDownload(options) {
  170. options.platform ??= detectBrowserPlatform();
  171. if (!options.platform) {
  172. throw new Error(`Cannot download a binary for the provided platform: ${os.platform()} (${os.arch()})`);
  173. }
  174. return await headHttpRequest(getDownloadUrl(options.browser, options.platform, options.buildId, options.baseUrl));
  175. }
  176. function getDownloadUrl(browser, platform, buildId, baseUrl) {
  177. return new URL(downloadUrls[browser](platform, buildId, baseUrl));
  178. }
  179. //# sourceMappingURL=install.js.map