Skip to content

Commit 8c6d6c6

Browse files
chore: manage packages installation by registry
1 parent c7fa5aa commit 8c6d6c6

File tree

12 files changed

+392
-235
lines changed

12 files changed

+392
-235
lines changed

src/browser-installer/constants.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ export const MIN_CHROMIUM_VERSION = 73;
1010
export const MIN_FIREFOX_VERSION = 60;
1111
export const MIN_EDGEDRIVER_VERSION = 94;
1212
export const DRIVER_WAIT_TIMEOUT = 10 * 1000; // 10s
13-
export const BYTES_PER_KILOBYTE = 1 << 10; // eslint-disable-line no-bitwise
14-
export const BYTES_PER_MEGABYTE = BYTES_PER_KILOBYTE << 10; // eslint-disable-line no-bitwise
13+
export const LINUX_UBUNTU_RELEASE_ID = "ubuntu";
1514
export const LINUX_RUNTIME_LIBRARIES_PATH_ENV_NAME = "LD_LIBRARY_PATH";
1615
export const MANDATORY_UBUNTU_PACKAGES_TO_BE_INSTALLED = ["fontconfig"];

src/browser-installer/install.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import _ from "lodash";
2-
import { Browser, getNormalizedBrowserName, type SupportedBrowser } from "./utils";
2+
import { Browser, browserInstallerDebug, getNormalizedBrowserName, type SupportedBrowser } from "./utils";
33

44
/**
55
* @returns path to installed browser binary
@@ -19,6 +19,15 @@ export const installBrowser = async (
1919

2020
const needToInstallUbuntuPackages = shouldInstallUbuntuPackages && (await isUbuntu());
2121

22+
browserInstallerDebug(
23+
[
24+
`install ${browserName}@${browserVersion}`,
25+
`shouldInstallWebDriver:${shouldInstallWebDriver}`,
26+
`shouldInstallUbuntuPackages:${shouldInstallUbuntuPackages}`,
27+
`needToInstallUbuntuPackages:${needToInstallUbuntuPackages}`,
28+
].join(", "),
29+
);
30+
2231
switch (browserName) {
2332
case Browser.CHROME:
2433
case Browser.CHROMIUM: {
@@ -83,6 +92,7 @@ const forceInstallBinaries = async (
8392
browserVersion?: string,
8493
): ForceInstallBinaryResult => {
8594
const normalizedBrowserName = getNormalizedBrowserName(browserName);
95+
const installOpts = { force: true, shouldInstallWebDriver: true, shouldInstallUbuntuPackages: true };
8696

8797
if (!normalizedBrowserName) {
8898
return {
@@ -91,7 +101,7 @@ const forceInstallBinaries = async (
91101
};
92102
}
93103

94-
return installFn(normalizedBrowserName, browserVersion, { force: true, shouldInstallWebDriver: true })
104+
return installFn(normalizedBrowserName, browserVersion, installOpts)
95105
.then(successResult => {
96106
return successResult
97107
? { status: BrowserInstallStatus.Ok }
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,39 @@
11
import { MultiBar, type SingleBar } from "cli-progress";
22
import type { DownloadProgressCallback } from "../utils";
3-
import { BYTES_PER_MEGABYTE } from "../constants";
43

54
export type RegisterProgressBarFn = (browserName: string, browserVersion: string) => DownloadProgressCallback;
65

7-
export const createBrowserDownloadProgressBar = (): { register: RegisterProgressBarFn } => {
6+
export const createBrowserDownloadProgressBar = (): { register: RegisterProgressBarFn; stop: () => void } => {
87
const progressBar = new MultiBar({
98
stopOnComplete: true,
109
forceRedraw: true,
1110
autopadding: true,
1211
hideCursor: true,
1312
fps: 5,
14-
format: " [{bar}] | {filename} | {value}/{total} MB",
13+
format: " [{bar}] | {filename} | {value}%",
1514
});
1615

1716
const register: RegisterProgressBarFn = (browserName, browserVersion) => {
1817
let bar: SingleBar;
1918

20-
const downloadProgressCallback: DownloadProgressCallback = (downloadedBytes, totalBytes) => {
19+
const downloadProgressCallback: DownloadProgressCallback = (done, total = 100) => {
2120
if (!bar) {
22-
const totalMB = Math.round((totalBytes / BYTES_PER_MEGABYTE) * 100) / 100;
23-
bar = progressBar.create(totalMB, 0, { filename: `${browserName}@${browserVersion}` });
21+
bar = progressBar.create(100, 0, { filename: `${browserName}@${browserVersion}` });
2422
}
2523

26-
const downloadedMB = Math.round((downloadedBytes / BYTES_PER_MEGABYTE) * 100) / 100;
24+
const downloadedPercents = Math.floor((done / total) * 100);
2725

28-
bar.update(downloadedMB);
26+
bar.update(downloadedPercents);
2927
};
3028

3129
return downloadProgressCallback;
3230
};
3331

34-
return { register };
32+
const stop = (): void => {
33+
progressBar.stop();
34+
};
35+
36+
process.once("exit", stop);
37+
38+
return { register, stop };
3539
};

src/browser-installer/registry/index.ts

Lines changed: 133 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { BrowserPlatform } from "@puppeteer/browsers";
2-
import { readJSONSync, outputJSONSync, existsSync } from "fs-extra";
2+
import _ from "lodash";
3+
import { outputJSONSync } from "fs-extra";
34
import path from "path";
45
import {
56
getRegistryPath,
7+
readRegistry,
68
browserInstallerDebug,
79
Driver,
810
Browser,
@@ -12,87 +14,123 @@ import {
1214
type SupportedBrowser,
1315
type SupportedDriver,
1416
type DownloadProgressCallback,
17+
type BinaryKey,
18+
type BinaryName,
19+
type OsName,
20+
type OsVersion,
21+
type OsPackagesKey,
1522
} from "../utils";
1623
import { getFirefoxBuildId } from "../firefox/utils";
1724
import logger from "../../utils/logger";
18-
import type { createBrowserDownloadProgressBar } from "./cli-progress-bar";
19-
20-
type VersionToPathMap = Record<string, string | Promise<string>>;
21-
type BinaryName = Exclude<SupportedBrowser | SupportedDriver, SupportedBrowser & SupportedDriver>;
22-
type RegistryKey = `${BinaryName}_${BrowserPlatform}`;
23-
type Registry = Record<RegistryKey, VersionToPathMap>;
2425

2526
const registryPath = getRegistryPath();
26-
const registry: Registry = existsSync(registryPath) ? readJSONSync(registryPath) : {};
27+
const registry = readRegistry(registryPath);
28+
29+
const getRegistryBinaryKey = (name: BinaryName, platform: BrowserPlatform): BinaryKey => `${name}_${platform}`;
30+
const getRegistryOsPackagesKey = (name: OsName, version: OsVersion): OsPackagesKey => `${name}_${version}`;
2731

28-
let cliProgressBar: ReturnType<typeof createBrowserDownloadProgressBar> | null = null;
29-
let warnedFirstTimeInstall = false;
32+
const saveRegistry = (): void => {
33+
const replacer = (_: string, value: unknown): unknown | undefined => {
34+
if ((value as Promise<unknown>).then) {
35+
return;
36+
}
3037

31-
const getRegistryKey = (name: BinaryName, platform: BrowserPlatform): RegistryKey => `${name}_${platform}`;
38+
return value;
39+
};
40+
41+
outputJSONSync(registryPath, registry, { replacer });
42+
};
43+
44+
const getCliProgressBar = _.once(async () => {
45+
const { createBrowserDownloadProgressBar } = await import("./cli-progress-bar");
46+
47+
return createBrowserDownloadProgressBar();
48+
});
3249

3350
export const getBinaryPath = async (name: BinaryName, platform: BrowserPlatform, version: string): Promise<string> => {
34-
const registryKey = getRegistryKey(name, platform);
51+
const registryKey = getRegistryBinaryKey(name, platform);
3552

36-
if (!registry[registryKey]) {
53+
if (!registry.binaries[registryKey]) {
3754
throw new Error(`Binary '${name}' on '${platform}' is not installed`);
3855
}
3956

40-
if (!registry[registryKey][version]) {
57+
if (!registry.binaries[registryKey][version]) {
4158
throw new Error(`Version '${version}' of driver '${name}' on '${platform}' is not installed`);
4259
}
4360

44-
const binaryRelativePath = await registry[registryKey][version];
61+
const binaryRelativePath = await registry.binaries[registryKey][version];
4562

4663
browserInstallerDebug(`resolved '${name}@${version}' on ${platform} to ${binaryRelativePath}`);
4764

4865
return path.resolve(registryPath, binaryRelativePath);
4966
};
5067

68+
export const getOsPackagesPath = async (name: OsName, version: OsVersion): Promise<string> => {
69+
const registryKey = getRegistryOsPackagesKey(name, version);
70+
71+
if (!registry.osPackages[registryKey]) {
72+
throw new Error(`Packages for ${name}@${version} are not installed`);
73+
}
74+
75+
const osPackagesRelativePath = await registry.osPackages[registryKey];
76+
77+
browserInstallerDebug(`resolved os packages for '${name}@${version}' to ${osPackagesRelativePath}`);
78+
79+
return path.resolve(registryPath, osPackagesRelativePath);
80+
};
81+
5182
const addBinaryToRegistry = (
5283
name: BinaryName,
5384
platform: BrowserPlatform,
5485
version: string,
5586
absoluteBinaryPath: string,
5687
): void => {
57-
const registryKey = getRegistryKey(name, platform);
88+
const registryKey = getRegistryBinaryKey(name, platform);
5889
const relativePath = path.relative(registryPath, absoluteBinaryPath);
5990

60-
registry[registryKey] ||= {};
61-
registry[registryKey][version] = relativePath;
91+
registry.binaries[registryKey] ||= {};
92+
registry.binaries[registryKey][version] = relativePath;
6293

63-
const replacer = (_: string, value: unknown): unknown | undefined => {
64-
if ((value as Promise<unknown>).then) {
65-
return;
66-
}
94+
browserInstallerDebug(`adding '${name}@${version}' on '${platform}' to registry at ${relativePath}`);
6795

68-
return value;
69-
};
96+
saveRegistry();
97+
};
7098

71-
browserInstallerDebug(`adding '${name}@${version}' on '${platform}' to registry at ${relativePath}`);
72-
outputJSONSync(registryPath, registry, { replacer });
99+
const addOsPackageToRegistry = (name: OsName, version: OsVersion, absolutePackagesDirPath: string): void => {
100+
const registryKey = getRegistryOsPackagesKey(name, version);
101+
const relativePath = path.relative(registryPath, absolutePackagesDirPath);
102+
103+
registry.osPackages[registryKey] = relativePath;
104+
105+
browserInstallerDebug(`adding os packages for '${name}@${version}' to registry at ${relativePath}`);
106+
107+
saveRegistry();
73108
};
74109

75110
const getBinaryVersions = (name: BinaryName, platform: BrowserPlatform): string[] => {
76-
const registryKey = getRegistryKey(name, platform);
111+
const registryKey = getRegistryBinaryKey(name, platform);
77112

78-
if (!registry[registryKey]) {
113+
if (!registry.binaries[registryKey]) {
79114
return [];
80115
}
81116

82-
return Object.keys(registry[registryKey]);
117+
return Object.keys(registry.binaries[registryKey]);
83118
};
84119

85120
const hasBinaryVersion = (name: BinaryName, platform: BrowserPlatform, version: string): boolean =>
86121
getBinaryVersions(name, platform).includes(version);
87122

123+
export const hasOsPackages = (name: OsName, version: OsVersion): boolean =>
124+
Boolean(registry.osPackages[getRegistryOsPackagesKey(name, version)]);
125+
88126
export const getMatchedDriverVersion = (
89127
driverName: SupportedDriver,
90128
platform: BrowserPlatform,
91129
browserVersion: string,
92130
): string | null => {
93-
const registryKey = getRegistryKey(driverName, platform);
131+
const registryKey = getRegistryBinaryKey(driverName, platform);
94132

95-
if (!registry[registryKey]) {
133+
if (!registry.binaries[registryKey]) {
96134
return null;
97135
}
98136

@@ -109,7 +147,7 @@ export const getMatchedDriverVersion = (
109147
}
110148

111149
if (driverName === Driver.GECKODRIVER) {
112-
const buildIds = Object.keys(registry[registryKey]);
150+
const buildIds = Object.keys(registry.binaries[registryKey]);
113151
const buildIdsSorted = buildIds.sort(semverVersionsComparator);
114152

115153
return buildIdsSorted.length ? buildIdsSorted[buildIdsSorted.length - 1] : null;
@@ -123,9 +161,9 @@ export const getMatchedBrowserVersion = (
123161
platform: BrowserPlatform,
124162
browserVersion: string,
125163
): string | null => {
126-
const registryKey = getRegistryKey(browserName, platform);
164+
const registryKey = getRegistryBinaryKey(browserName, platform);
127165

128-
if (!registry[registryKey]) {
166+
if (!registry.binaries[registryKey]) {
129167
return null;
130168
}
131169

@@ -170,46 +208,88 @@ export const getMatchedBrowserVersion = (
170208
return suitableBuildIdsSorted[suitableBuildIdsSorted.length - 1];
171209
};
172210

211+
const logDownloadingOsPackagesWarningOnce = _.once((osName: string) => {
212+
logger.warn(`Downloading extra ${osName} packages`);
213+
});
214+
215+
const logDownloadingBrowsersWarningOnce = _.once(() => {
216+
logger.warn("Downloading Testplane browsers");
217+
logger.warn("Note: this is one-time action. It may take a while...");
218+
});
219+
173220
export const installBinary = async (
174221
name: BinaryName,
175222
platform: BrowserPlatform,
176223
version: string,
177224
installFn: (downloadProgressCallback: DownloadProgressCallback) => Promise<string>,
178225
): Promise<string> => {
179-
const registryKey = getRegistryKey(name, platform);
226+
const registryKey = getRegistryBinaryKey(name, platform);
180227

181228
if (hasBinaryVersion(name, platform, version)) {
182229
return getBinaryPath(name, platform, version);
183230
}
184231

185232
browserInstallerDebug(`installing '${name}@${version}' on '${platform}'`);
186233

187-
if (!cliProgressBar) {
188-
const { createBrowserDownloadProgressBar } = await import("./cli-progress-bar");
234+
const progressBar = await getCliProgressBar();
189235

190-
cliProgressBar = createBrowserDownloadProgressBar();
191-
}
192-
193-
const originalDownloadProgressCallback = cliProgressBar.register(name, version);
236+
const originalDownloadProgressCallback = progressBar.register(name, version);
194237
const downloadProgressCallback: DownloadProgressCallback = (...args) => {
195-
if (!warnedFirstTimeInstall) {
196-
logger.warn("Downloading Testplane browsers");
197-
logger.warn("Note: this is one-time action. It may take a while...");
198-
199-
warnedFirstTimeInstall = true;
200-
}
238+
logDownloadingBrowsersWarningOnce();
201239

202240
return originalDownloadProgressCallback(...args);
203241
};
204242

205-
const installPromise = installFn(downloadProgressCallback).then(executablePath => {
206-
addBinaryToRegistry(name, platform, version, executablePath);
243+
const installPromise = installFn(downloadProgressCallback)
244+
.then(executablePath => {
245+
addBinaryToRegistry(name, platform, version, executablePath);
246+
247+
return executablePath;
248+
})
249+
.catch(err => {
250+
progressBar?.stop();
251+
252+
throw err;
253+
});
254+
255+
registry.binaries[registryKey] ||= {};
256+
registry.binaries[registryKey][version] = installPromise;
257+
258+
return installPromise;
259+
};
260+
261+
export const installOsPackages = async (
262+
osName: OsName,
263+
version: OsVersion,
264+
installFn: (downloadProgressCallback: DownloadProgressCallback) => Promise<string>,
265+
): Promise<string> => {
266+
const registryKey = getRegistryOsPackagesKey(osName, version);
267+
268+
if (hasOsPackages(osName, version)) {
269+
return getOsPackagesPath(osName, version);
270+
}
271+
272+
browserInstallerDebug(`installing os packages for '${osName}@${version}'`);
273+
274+
logDownloadingOsPackagesWarningOnce(osName);
275+
276+
const progressBar = await getCliProgressBar();
277+
278+
const downloadProgressCallback = progressBar.register(`extra packages for ${osName}`, version);
279+
280+
const installPromise = installFn(downloadProgressCallback)
281+
.then(packagesPath => {
282+
addOsPackageToRegistry(osName, version, packagesPath);
283+
284+
return packagesPath;
285+
})
286+
.catch(err => {
287+
progressBar.stop();
207288

208-
return executablePath;
209-
});
289+
throw err;
290+
});
210291

211-
registry[registryKey] ||= {};
212-
registry[registryKey][version] = installPromise;
292+
registry.osPackages[registryKey] = installPromise;
213293

214294
return installPromise;
215295
};

0 commit comments

Comments
 (0)