Skip to content

Commit 742281f

Browse files
authored
Merge pull request presenton#627 from presenton/fix/chromium-download
refactor: Enhance Chromium export utility functions
2 parents 1d53797 + 5d1f329 commit 742281f

1 file changed

Lines changed: 116 additions & 33 deletions

File tree

‎electron/app/utils/export-chromium.ts‎

Lines changed: 116 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import fs from "fs";
22
import os from "os";
33
import path from "path";
4-
import { Browser, detectBrowserPlatform, install } from "@puppeteer/browsers";
4+
import {
5+
Browser,
6+
Cache,
7+
computeExecutablePath,
8+
detectBrowserPlatform,
9+
install,
10+
} from "@puppeteer/browsers";
511
import { safeLog } from "./safe-console";
612

713
/** Must match the Chrome revision expected by the bundled presentation-export runtime. */
@@ -22,55 +28,135 @@ function resolvePuppeteerCacheRoot(): string {
2228
return path.join(os.homedir(), ".cache", "puppeteer");
2329
}
2430

25-
function resolvePuppeteerChromeBaseDir(): string {
26-
return path.join(resolvePuppeteerCacheRoot(), "chrome");
31+
function resolveExportChromeInstallOptions():
32+
| { browser: Browser.CHROME; buildId: string; cacheDir: string; platform: NonNullable<ReturnType<typeof detectBrowserPlatform>> }
33+
| null {
34+
const platform = detectBrowserPlatform();
35+
if (!platform) {
36+
return null;
37+
}
38+
return {
39+
browser: Browser.CHROME,
40+
buildId: EXPORT_CHROME_BUILD_ID,
41+
cacheDir: resolvePuppeteerCacheRoot(),
42+
platform,
43+
};
2744
}
2845

29-
function getExpectedExecutableRelativePath(): string {
46+
/** Pre–Chrome-for-Testing cache layouts still present on some machines. */
47+
function getLegacyExecutableRelativePaths(): string[] {
3048
if (process.platform === "win32") {
31-
return path.join("chrome-win64", "chrome.exe");
49+
return [
50+
path.join("chrome-win64", "chrome.exe"),
51+
path.join("chrome-win32", "chrome.exe"),
52+
];
3253
}
3354
if (process.platform === "darwin") {
34-
return path.join("chrome-mac", "Chromium.app", "Contents", "MacOS", "Chromium");
55+
return [
56+
path.join("chrome-mac", "Chromium.app", "Contents", "MacOS", "Chromium"),
57+
path.join("chrome-mac-arm64", "Chromium.app", "Contents", "MacOS", "Chromium"),
58+
path.join("chrome-mac-x64", "Chromium.app", "Contents", "MacOS", "Chromium"),
59+
];
3560
}
36-
return path.join("chrome-linux64", "chrome");
61+
return [path.join("chrome-linux64", "chrome")];
3762
}
3863

39-
function getChromeRevisionDirectories(): string[] {
40-
const chromeBaseDir = resolvePuppeteerChromeBaseDir();
64+
function resolveLegacyInstalledExportChromiumPath(): string | null {
65+
const chromeBaseDir = path.join(resolvePuppeteerCacheRoot(), "chrome");
66+
let revisionDirs: string[] = [];
4167
try {
42-
const entries = fs.readdirSync(chromeBaseDir, { withFileTypes: true });
43-
return entries
68+
revisionDirs = fs
69+
.readdirSync(chromeBaseDir, { withFileTypes: true })
4470
.filter((entry) => entry.isDirectory())
4571
.map((entry) => path.join(chromeBaseDir, entry.name));
4672
} catch {
47-
return [];
73+
return null;
74+
}
75+
76+
const legacyRelativePaths = getLegacyExecutableRelativePaths();
77+
for (const revisionDir of revisionDirs) {
78+
for (const relativePath of legacyRelativePaths) {
79+
const executablePath = path.join(revisionDir, relativePath);
80+
if (fs.existsSync(executablePath)) {
81+
return executablePath;
82+
}
83+
}
4884
}
85+
return null;
4986
}
5087

5188
export function resolveInstalledExportChromiumPath(): string | null {
52-
const executableRelativePath = getExpectedExecutableRelativePath();
53-
for (const revisionDir of getChromeRevisionDirectories()) {
54-
const executablePath = path.join(revisionDir, executableRelativePath);
55-
if (fs.existsSync(executablePath)) {
56-
return executablePath;
89+
const options = resolveExportChromeInstallOptions();
90+
if (options) {
91+
const expectedPath = computeExecutablePath(options);
92+
if (fs.existsSync(expectedPath)) {
93+
return expectedPath;
94+
}
95+
96+
const cache = new Cache(options.cacheDir);
97+
for (const installed of cache.getInstalledBrowsers()) {
98+
if (installed.browser !== Browser.CHROME || installed.buildId !== options.buildId) {
99+
continue;
100+
}
101+
if (fs.existsSync(installed.executablePath)) {
102+
return installed.executablePath;
103+
}
57104
}
58105
}
59-
return null;
106+
107+
return resolveLegacyInstalledExportChromiumPath();
60108
}
61109

62110
export function isExportChromiumAvailable(): boolean {
63111
return Boolean(resolveInstalledExportChromiumPath());
64112
}
65113

66114
export async function removeBrokenExportChromiumCaches(): Promise<number> {
67-
const executableRelativePath = getExpectedExecutableRelativePath();
115+
const cacheDir = resolvePuppeteerCacheRoot();
116+
const cache = new Cache(cacheDir);
68117
let removedCount = 0;
69-
for (const revisionDir of getChromeRevisionDirectories()) {
70-
const executablePath = path.join(revisionDir, executableRelativePath);
71-
if (fs.existsSync(executablePath)) {
118+
119+
for (const installed of cache.getInstalledBrowsers()) {
120+
if (installed.browser !== Browser.CHROME) {
121+
continue;
122+
}
123+
if (fs.existsSync(installed.executablePath)) {
124+
continue;
125+
}
126+
try {
127+
await fs.promises.rm(installed.path, { recursive: true, force: true });
128+
removedCount += 1;
129+
safeLog(`[Chromium] Removed broken cache: ${installed.path}`);
130+
} catch {
131+
// Best effort cleanup only.
132+
}
133+
}
134+
135+
const chromeBaseDir = path.join(cacheDir, "chrome");
136+
const legacyRelativePaths = getLegacyExecutableRelativePaths();
137+
let revisionDirs: string[] = [];
138+
try {
139+
revisionDirs = fs
140+
.readdirSync(chromeBaseDir, { withFileTypes: true })
141+
.filter((entry) => entry.isDirectory())
142+
.map((entry) => path.join(chromeBaseDir, entry.name));
143+
} catch {
144+
return removedCount;
145+
}
146+
147+
for (const revisionDir of revisionDirs) {
148+
const hasLegacyExecutable = legacyRelativePaths.some((relativePath) =>
149+
fs.existsSync(path.join(revisionDir, relativePath))
150+
);
151+
if (hasLegacyExecutable) {
152+
continue;
153+
}
154+
155+
const basename = path.basename(revisionDir);
156+
if (basename.includes("-")) {
72157
continue;
73158
}
159+
74160
try {
75161
await fs.promises.rm(revisionDir, { recursive: true, force: true });
76162
removedCount += 1;
@@ -79,6 +165,7 @@ export async function removeBrokenExportChromiumCaches(): Promise<number> {
79165
// Best effort cleanup only.
80166
}
81167
}
168+
82169
return removedCount;
83170
}
84171

@@ -102,27 +189,22 @@ export async function installExportChromium(
102189
return;
103190
}
104191

105-
const platform = detectBrowserPlatform();
106-
if (!platform) {
192+
const options = resolveExportChromeInstallOptions();
193+
if (!options) {
107194
throw new Error(`Unsupported platform for Chromium export runtime: ${process.platform}-${process.arch}`);
108195
}
109196

110-
const cacheDir = resolvePuppeteerCacheRoot();
111-
await fs.promises.mkdir(cacheDir, { recursive: true });
197+
await fs.promises.mkdir(options.cacheDir, { recursive: true });
112198

113-
const buildId = EXPORT_CHROME_BUILD_ID;
114199
onProgress?.({
115200
phase: "downloading",
116201
percent: 0,
117-
message: `Downloading Chromium ${buildId}…`,
202+
message: `Downloading Chromium ${options.buildId}…`,
118203
});
119204

120205
let lastLoggedPercent = -1;
121206
await install({
122-
browser: Browser.CHROME,
123-
buildId,
124-
cacheDir,
125-
platform,
207+
...options,
126208
downloadProgressCallback(downloadedBytes, totalBytes) {
127209
if (totalBytes <= 0) {
128210
return;
@@ -141,8 +223,9 @@ export async function installExportChromium(
141223
});
142224

143225
if (!isExportChromiumAvailable()) {
226+
const expectedPath = computeExecutablePath(options);
144227
throw new Error(
145-
"Chromium download finished but chrome executable was not found. Check your network connection and try again."
228+
`Chromium download finished but chrome executable was not found at ${expectedPath}. Check your network connection and try again.`
146229
);
147230
}
148231

0 commit comments

Comments
 (0)