11import fs from "fs" ;
22import os from "os" ;
33import 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" ;
511import { 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
5188export 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
62110export function isExportChromiumAvailable ( ) : boolean {
63111 return Boolean ( resolveInstalledExportChromiumPath ( ) ) ;
64112}
65113
66114export 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